<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
     
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">
    
      
      <a class="forkMe" href="https://github.com/Shen-Yu/hexo-theme-ayer"
        target="_blank"><img width="149" height="149" src="/images/forkme.png"
          class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
    
  <div class="cover-frame">
    <div class="bg-box">
      <img src="/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/">Hexo</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['面朝大海，春暖花开', '何来天才，唯有苦练', '集中一点，登峰造极'],
        startDelay: 0,
        typeSpeed: 200,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-develop/搞定python多线程和多进程"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/develop/%E6%90%9E%E5%AE%9Apython%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%A4%9A%E8%BF%9B%E7%A8%8B/"
    >搞定python多线程和多进程.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/%E6%90%9E%E5%AE%9Apython%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%A4%9A%E8%BF%9B%E7%A8%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="搞定python多线程和多进程"><a href="#搞定python多线程和多进程" class="headerlink" title="搞定python多线程和多进程"></a>搞定python多线程和多进程</h1><blockquote>
<p>来源:<a target="_blank" rel="noopener" href="https://www.cnblogs.com/whatisfantasy/p/6440585.html">https://www.cnblogs.com/whatisfantasy/p/6440585.html</a></p>
</blockquote>
<p>###1 概念梳理：</p>
<h4 id="1-1-线程"><a href="#1-1-线程" class="headerlink" title="1.1 线程"></a>1.1 线程</h4><h5 id="1-1-1-什么是线程"><a href="#1-1-1-什么是线程" class="headerlink" title="1.1.1 什么是线程"></a>1.1.1 什么是线程</h5><p><strong>线程</strong>是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务。一个线程是一个execution context（执行上下文），即一个cpu执行时所需要的一串指令。</p>
<h5 id="1-1-2-线程的工作方式"><a href="#1-1-2-线程的工作方式" class="headerlink" title="1.1.2 线程的工作方式"></a>1.1.2 线程的工作方式</h5><p>假设你正在读一本书，没有读完，你想休息一下，但是你想在回来时恢复到当时读的具体进度。有一个方法就是记下页数、行数与字数这三个数值，这些数值就是execution context。如果你的室友在你休息的时候，使用相同的方法读这本书。你和她只需要这三个数字记下来就可以在交替的时间共同阅读这本书了。</p>
<p>线程的工作方式与此类似。CPU会给你一个在同一时间能够做多个运算的幻觉，实际上它在每个运算上只花了极少的时间，本质上CPU同一时刻只干了一件事。它能这样做就是因为它有每个运算的execution context。就像你能够和你朋友共享同一本书一样，多任务也能共享同一块CPU。</p>
<h4 id="1-2-进程"><a href="#1-2-进程" class="headerlink" title="1.2 进程"></a>1.2 进程</h4><p>一个程序的执行实例就是一个<strong>进程</strong>。每一个进程提供执行程序所需的所有资源。（进程本质上是资源的集合）</p>
<p>一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文（记录启动该进程的用户和权限等等）、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间（内存空间），还要有至少一个线程。</p>
<p>每一个进程启动时都会最先产生一个线程，即主线程。然后主线程会再创建其他的子线程。</p>
<blockquote>
<p>与进程相关的资源包括:</p>
</blockquote>
<ul>
<li>内存页（<strong>同一个进程中的所有线程共享同一个内存空间</strong>）</li>
<li>文件描述符(e.g. open sockets)</li>
<li>安全凭证（e.g.启动该进程的用户ID）</li>
</ul>
<h4 id="1-3-进程与线程区别"><a href="#1-3-进程与线程区别" class="headerlink" title="1.3 进程与线程区别"></a>1.3 进程与线程区别</h4><p>1.同一个进程中的线程共享同一内存空间，但是进程之间是独立的。<br>2.同一个进程中的所有线程的数据是共享的（进程通讯），进程之间的数据是独立的。<br>3.对主线程的修改可能会影响其他线程的行为，但是父进程的修改（除了删除以外）不会影响其他子进程。<br>4.线程是一个上下文的执行指令，而进程则是与运算相关的一簇资源。<br>5.同一个进程的线程之间可以直接通信，但是进程之间的交流需要借助中间代理来实现。<br>6.创建新的线程很容易，但是创建新的进程需要对父进程做一次复制。<br>7.一个线程可以操作同一进程的其他线程，但是进程只能操作其子进程。<br>8.线程启动速度快，进程启动速度慢（但是两者运行速度没有可比性）。</p>
<h3 id="2-多线程"><a href="#2-多线程" class="headerlink" title="2 多线程"></a>2 多线程</h3><h4 id="2-1-线程常用方法"><a href="#2-1-线程常用方法" class="headerlink" title="2.1 线程常用方法"></a>2.1 线程常用方法</h4><table>
<thead>
<tr>
<th>方法</th>
<th>注释</th>
</tr>
</thead>
<tbody><tr>
<td>start()</td>
<td>线程准备就绪，等待CPU调度</td>
</tr>
<tr>
<td>setName()</td>
<td>为线程设置名称</td>
</tr>
<tr>
<td>getName()</td>
<td>获取线程名称</td>
</tr>
<tr>
<td>setDaemon(True)</td>
<td>设置为守护线程</td>
</tr>
<tr>
<td>join()</td>
<td>逐个执行每个线程，执行完毕后继续往下执行</td>
</tr>
<tr>
<td>run()</td>
<td>线程被cpu调度后自动执行线程对象的run方法，如果想自定义线程类，直接重写run方法就行了</td>
</tr>
</tbody></table>
<h5 id="2-1-1-Thread类"><a href="#2-1-1-Thread类" class="headerlink" title="2.1.1 Thread类"></a>2.1.1 Thread类</h5><p>1.普通创建方式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;0s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line"></span><br><span class="line">t1 &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t1&quot;,))</span><br><span class="line">t2 &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t2&quot;,))</span><br><span class="line">t1.start()</span><br><span class="line">t2.start()</span><br><span class="line"></span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t1</span><br><span class="line">task t2</span><br><span class="line">2s</span><br><span class="line">2s</span><br><span class="line">1s</span><br><span class="line">1s</span><br><span class="line">0s</span><br><span class="line">0s</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>2.继承threading.Thread来自定义线程类<br>其本质是重构Thread类中的run方法</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class MyThread(threading.Thread):</span><br><span class="line">    def __init__(self, n):</span><br><span class="line">        super(MyThread, self).__init__()  # 重构run函数必须要写</span><br><span class="line">        self.n &#x3D; n</span><br><span class="line"></span><br><span class="line">    def run(self):</span><br><span class="line">        print(&quot;task&quot;, self.n)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;2s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;1s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;0s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">if __name__ &#x3D;&#x3D; &quot;__main__&quot;:</span><br><span class="line">    t1 &#x3D; MyThread(&quot;t1&quot;)</span><br><span class="line">    t2 &#x3D; MyThread(&quot;t2&quot;)</span><br><span class="line"></span><br><span class="line">    t1.start()</span><br><span class="line">    t2.start()</span><br></pre></td></tr></table></figure>

<h5 id="2-1-2-计算子线程执行的时间"><a href="#2-1-2-计算子线程执行的时间" class="headerlink" title="2.1.2 计算子线程执行的时间"></a>2.1.2 计算子线程执行的时间</h5><p>注：sleep的时候是不会占用cpu的,在sleep的时候操作系统会把线程暂时挂起。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">join()  #等此线程执行完后，再执行其他线程或主线程</span><br><span class="line">threading.current_thread()      #输出当前线程</span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n,threading.current_thread())    #输出当前的线程</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;3s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1s&#39;)</span><br><span class="line"></span><br><span class="line">strat_time &#x3D; time.time()</span><br><span class="line"></span><br><span class="line">t_obj &#x3D; []   #定义列表用于存放子线程实例</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line">    </span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">由主线程生成的三个子线程</span><br><span class="line">task t-0 &lt;Thread(Thread-1, started 44828)&gt;</span><br><span class="line">task t-1 &lt;Thread(Thread-2, started 42804)&gt;</span><br><span class="line">task t-2 &lt;Thread(Thread-3, started 41384)&gt;</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line">for tmp in t_obj:</span><br><span class="line">    t.join()            #为每个子线程添加join之后，主线程就会等这些子线程执行完之后再执行。</span><br><span class="line"></span><br><span class="line">print(&quot;cost:&quot;, time.time() - strat_time) #主线程</span><br><span class="line"></span><br><span class="line">print(threading.current_thread())       #输出当前线程</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">&lt;_MainThread(MainThread, started 43740)&gt;</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-1-3-统计当前活跃的线程数"><a href="#2-1-3-统计当前活跃的线程数" class="headerlink" title="2.1.3 统计当前活跃的线程数"></a>2.1.3 统计当前活跃的线程数</h4><p>由于主线程比子线程快很多，当主线程执行active_count()时，其他子线程都还没执行完毕，因此利用主线程统计的活跃的线程数num = sub_num(子线程数量)+1(主线程本身)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)    </span><br><span class="line">    time.sleep(1)       #此时子线程停1s</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(0.5)     #主线程停0.5秒</span><br><span class="line">print(threading.active_count()) #输出当前活跃的线程数</span><br><span class="line"></span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">4</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>由于主线程比子线程慢很多，当主线程执行active_count()时，其他子线程都已经执行完毕，因此利用主线程统计的活跃的线程数num = 1(主线程本身)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(0.5)       #此时子线程停0.5s</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(1)     #主线程停1秒</span><br><span class="line">print(threading.active_count()) #输出活跃的线程数</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">1</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>此外我们还能发现在python内部默认会等待最后一个进程执行完后再执行exit()，或者说python内部在此时有一个隐藏的join()。</p>
<h4 id="2-2-守护进程"><a href="#2-2-守护进程" class="headerlink" title="2.2 守护进程"></a>2.2 守护进程</h4><p>我们看下面这个例子，这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程，因此当主进程结束后，子线程也会随之结束。所以当主线程结束后，整个程序就退出了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(1)       #此时子线程停1s</span><br><span class="line">    print(&#39;3&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1&#39;)</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.setDaemon(True)   #把子进程设置为守护线程，必须在start()之前设置</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(0.5)     #主线程停0.5秒</span><br><span class="line">print(threading.active_count()) #输出活跃的线程数</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">4</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-3-GIL"><a href="#2-3-GIL" class="headerlink" title="2.3 GIL"></a>2.3 GIL</h4><p>在非python环境中，单核情况下，同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中，无论有多少核，同时只能执行一个线程。究其原因，这就是由于GIL的存在导致的。</p>
<p>GIL的全称是Global Interpreter Lock(全局解释器锁)，来源是python设计之初的考虑，为了数据安全所做的决定。某个线程想要执行，必须先拿到GIL，我们可以把GIL看作是“通行证”，并且在一个python进程中，GIL只有一个。拿不到通行证的线程，就不允许进入CPU执行。GIL只在cpython中才有，因为cpython调用的是c语言的原生线程，所以他不能直接操作cpu，只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。</p>
<p><strong>Python多线程的工作过程：</strong><br>python在使用多线程的时候，调用的是c语言的原生线程。</p>
<ol>
<li>拿到公共数据</li>
<li>申请gil</li>
<li>python解释器调用os原生线程</li>
<li>os操作cpu执行运算</li>
<li>当该线程执行时间到后，无论运算是否已经执行完，gil都被要求释放</li>
<li>进而由其他进程重复上面的过程</li>
<li>等其他进程执行完后，又会切换到之前的线程（从他记录的上下文继续执行）<br>整个过程是每个线程执行自己的运算，当执行时间到就进行切换（context switch）。</li>
</ol>
<ul>
<li>python针对不同类型的代码执行效率也是不同的：</li>
</ul>
<blockquote>
<p>1、CPU密集型代码(各种循环处理、计算等等)，在这种情况下，由于计算工作多，ticks计数很快就会达到阈值，然后触发GIL的释放与再竞争（多个线程来回切换当然是需要消耗资源的），所以python下的多线程对CPU密集型代码并不友好。<br>2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作)，多线程能够有效提升效率(单线程下有IO操作会进行IO等待，造成不必要的时间浪费，而开启多线程能在线程A等待时，自动切换到线程B，可以不浪费CPU的资源，从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。</p>
</blockquote>
<ul>
<li>使用建议？</li>
</ul>
<blockquote>
<p>python下想要充分利用多核CPU，就用多进程。因为每个进程有各自独立的GIL，互不干扰，这样就可以真正意义上的并行执行，在python中，多进程的执行效率优于多线程(仅仅针对多核CPU而言)。</p>
</blockquote>
<ul>
<li>GIL在python中的版本差异：</li>
</ul>
<blockquote>
<p>1、在python2.x里，GIL的释放逻辑是当前线程遇见<code>IO操作</code>或者<code>ticks计数达到100</code>时进行释放。（ticks可以看作是python自身的一个计数器，专门做用于GIL，每次释放后归零，这个计数可以通过sys.setcheckinterval 来调整）。而每次释放GIL锁，线程进行锁竞争、切换线程，会消耗资源。并且由于GIL锁存在，python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行)，这就是为什么在多核CPU上，python的多线程效率并不高。<br>2、在python3.x中，GIL不使用ticks计数，改为使用计时器（执行时间达到阈值后，当前线程释放GIL），这样对CPU密集型程序更加友好，但依然没有解决GIL导致的同一时间只能执行一个线程的问题，所以效率依然不尽如人意。</p>
</blockquote>
<h4 id="2-4-线程锁"><a href="#2-4-线程锁" class="headerlink" title="2.4 线程锁"></a>2.4 线程锁</h4><p>由于线程之间是进行随机调度，并且每个线程可能只执行n条执行之后，当多个线程同时修改同一条数据时可能会出现脏数据，所以，出现了线程锁，即同一时刻允许一个线程执行操作。线程锁用于锁定资源，你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时，任何一个锁都可以锁这个资源，就好比你用不同的锁都可以把相同的一个门锁住是一个道理。</p>
<p>由于线程之间是进行随机调度，如果有多个线程同时操作一个对象，如果没有很好地保护该对象，会造成程序结果的不可预期，我们也称此为“线程不安全”。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#实测：在python2.7、mac os下，运行以下代码可能会产生脏数据。但是在python3中就不一定会出现下面的问题。</span><br><span class="line"></span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    global num</span><br><span class="line">    num +&#x3D; 1</span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">t_obj &#x3D; [] </span><br><span class="line"></span><br><span class="line">for i in range(20000):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line"></span><br><span class="line">for t in t_obj:</span><br><span class="line">    t.join()</span><br><span class="line"></span><br><span class="line">print &quot;num:&quot;, num</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">产生脏数据后的运行结果：</span><br><span class="line">num: 19999</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-5-互斥锁（mutex）"><a href="#2-5-互斥锁（mutex）" class="headerlink" title="2.5 互斥锁（mutex）"></a>2.5 互斥锁（mutex）</h4><p>为了方式上面情况的发生，就出现了互斥锁(Lock)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    lock.acquire()  #获取锁</span><br><span class="line">    global num</span><br><span class="line">    num +&#x3D; 1</span><br><span class="line">    lock.release()  #释放锁</span><br><span class="line"></span><br><span class="line">lock &#x3D; threading.Lock()     #实例化一个锁对象</span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">t_obj &#x3D; []  </span><br><span class="line"></span><br><span class="line">for i in range(20000):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line"></span><br><span class="line">for t in t_obj:</span><br><span class="line">    t.join()</span><br><span class="line"></span><br><span class="line">print &quot;num:&quot;, num</span><br></pre></td></tr></table></figure>

<h4 id="2-6-递归锁"><a href="#2-6-递归锁" class="headerlink" title="2.6 递归锁"></a>2.6 递归锁</h4><p>RLcok类的用法和Lock类一模一样，但它支持嵌套，，在多个锁没有释放的时候一般会使用使用RLcok类。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line">   </span><br><span class="line">gl_num &#x3D; 0</span><br><span class="line">   </span><br><span class="line">lock &#x3D; threading.RLock()</span><br><span class="line">   </span><br><span class="line">def Func():</span><br><span class="line">    lock.acquire()</span><br><span class="line">    global gl_num</span><br><span class="line">    gl_num +&#x3D;1</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print gl_num</span><br><span class="line">    lock.release()</span><br><span class="line">       </span><br><span class="line">for i in range(10):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;Func)</span><br><span class="line">    t.start()</span><br></pre></td></tr></table></figure>

<h4 id="2-7-信号量（BoundedSemaphore类）"><a href="#2-7-信号量（BoundedSemaphore类）" class="headerlink" title="2.7 信号量（BoundedSemaphore类）"></a>2.7 信号量（BoundedSemaphore类）</h4><p>互斥锁同时只允许一个线程更改数据，而Semaphore是同时允许一定数量的线程更改数据 ，比如厕所有3个坑，那最多只允许3个人上厕所，后面的人只能等里面有人出来了才能再进去。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    semaphore.acquire()   #加锁</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&quot;run the thread:%s\n&quot; % n)</span><br><span class="line">    semaphore.release()     #释放</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">semaphore &#x3D; threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行</span><br><span class="line"></span><br><span class="line">for i in range(22):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">while threading.active_count() !&#x3D; 1:</span><br><span class="line">    pass  # print threading.active_count()</span><br><span class="line">else:</span><br><span class="line">    print(&#39;-----all threads done-----&#39;)</span><br></pre></td></tr></table></figure>

<h3 id="2-8-事件（Event类）"><a href="#2-8-事件（Event类）" class="headerlink" title="2.8 事件（Event类）"></a>2.8 事件（Event类）</h3><p>python线程的<strong>事件</strong>用于主线程控制其他线程的执行，事件是一个简单的线程<strong>同步</strong>对象，其主要提供以下几个方法：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>注释</th>
</tr>
</thead>
<tbody><tr>
<td>clear</td>
<td>将flag设置为“False”</td>
</tr>
<tr>
<td>set</td>
<td>将flag设置为“True”</td>
</tr>
<tr>
<td>is_set</td>
<td>判断是否设置了flag</td>
</tr>
<tr>
<td>wait</td>
<td>会一直监听flag，如果没有检测到flag就一直处于阻塞状态</td>
</tr>
</tbody></table>
<p>事件处理的机制：全局定义了一个“Flag”，当flag值为“False”，那么event.wait()就会阻塞，当flag值为“True”，那么event.wait()便不再阻塞。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">#利用Event类模拟红绿灯</span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">event &#x3D; threading.Event()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def lighter():</span><br><span class="line">    count &#x3D; 0</span><br><span class="line">    event.set()     #初始值为绿灯</span><br><span class="line">    while True:</span><br><span class="line">        if 5 &lt; count &lt;&#x3D;10 :</span><br><span class="line">            event.clear()  # 红灯，清除标志位</span><br><span class="line">            print(&quot;\33[41;1mred light is on...\033[0m&quot;)</span><br><span class="line">        elif count &gt; 10:</span><br><span class="line">            event.set()  # 绿灯，设置标志位</span><br><span class="line">            count &#x3D; 0</span><br><span class="line">        else:</span><br><span class="line">            print(&quot;\33[42;1mgreen light is on...\033[0m&quot;)</span><br><span class="line"></span><br><span class="line">        time.sleep(1)</span><br><span class="line">        count +&#x3D; 1</span><br><span class="line"></span><br><span class="line">def car(name):</span><br><span class="line">    while True:</span><br><span class="line">        if event.is_set():      #判断是否设置了标志位</span><br><span class="line">            print(&quot;[%s] running...&quot;%name)</span><br><span class="line">            time.sleep(1)</span><br><span class="line">        else:</span><br><span class="line">            print(&quot;[%s] sees red light,waiting...&quot;%name)</span><br><span class="line">            event.wait()</span><br><span class="line">            print(&quot;[%s] green light is on,start going...&quot;%name)</span><br><span class="line"></span><br><span class="line">light &#x3D; threading.Thread(target&#x3D;lighter,)</span><br><span class="line">light.start()</span><br><span class="line"></span><br><span class="line">car &#x3D; threading.Thread(target&#x3D;car,args&#x3D;(&quot;MINI&quot;,))</span><br><span class="line">car.start()</span><br></pre></td></tr></table></figure>

<h4 id="2-9-条件（Condition类）"><a href="#2-9-条件（Condition类）" class="headerlink" title="2.9 条件（Condition类）"></a>2.9 条件（Condition类）</h4><p>使得线程等待，只有满足某条件时，才释放n个线程</p>
<h4 id="2-10-定时器（Timer类）"><a href="#2-10-定时器（Timer类）" class="headerlink" title="2.10 定时器（Timer类）"></a>2.10 定时器（Timer类）</h4><p>定时器，指定n秒后执行某操作</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">from threading import Timer</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">def hello():</span><br><span class="line">    print(&quot;hello, world&quot;)</span><br><span class="line"> </span><br><span class="line">t &#x3D; Timer(1, hello)</span><br><span class="line">t.start()  # after 1 seconds, &quot;hello, world&quot; will be printed</span><br></pre></td></tr></table></figure>

<h3 id="3-多进程"><a href="#3-多进程" class="headerlink" title="3 多进程"></a>3 多进程</h3><p>在linux中，每个进程都是由父进程提供的。每启动一个子进程就从父进程克隆一份数据，但是进程之间的数据本身是不能共享的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process</span><br><span class="line">import time</span><br><span class="line">def f(name):</span><br><span class="line">    time.sleep(2)</span><br><span class="line">    print(&#39;hello&#39;, name)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(&#39;bob&#39;,))</span><br><span class="line">    p.start()</span><br><span class="line">    p.join()</span><br><span class="line">from multiprocessing import Process</span><br><span class="line">import os</span><br><span class="line"> </span><br><span class="line">def info(title):</span><br><span class="line">    print(title)</span><br><span class="line">    print(&#39;module name:&#39;, __name__)</span><br><span class="line">    print(&#39;parent process:&#39;, os.getppid())  #获取父进程id</span><br><span class="line">    print(&#39;process id:&#39;, os.getpid())   #获取自己的进程id</span><br><span class="line">    print(&quot;\n\n&quot;)</span><br><span class="line"> </span><br><span class="line">def f(name):</span><br><span class="line">    info(&#39;\033[31;1mfunction f\033[0m&#39;)</span><br><span class="line">    print(&#39;hello&#39;, name)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    info(&#39;\033[32;1mmain process line\033[0m&#39;)</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(&#39;bob&#39;,))</span><br><span class="line">    p.start()</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h4 id="3-1-进程间通信"><a href="#3-1-进程间通信" class="headerlink" title="3.1 进程间通信"></a>3.1 进程间通信</h4><p>由于进程之间数据是不共享的，所以不会出现多线程GIL带来的问题。多进程之间的通信通过Queue()或Pipe()来实现</p>
<h5 id="3-1-1-Queue"><a href="#3-1-1-Queue" class="headerlink" title="3.1.1 Queue()"></a>3.1.1 Queue()</h5><p>使用方法跟threading里的queue差不多</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Queue</span><br><span class="line"> </span><br><span class="line">def f(q):</span><br><span class="line">    q.put([42, None, &#39;hello&#39;])</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    q &#x3D; Queue()</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(q,))</span><br><span class="line">    p.start()</span><br><span class="line">    print(q.get())    # prints &quot;[42, None, &#39;hello&#39;]&quot;</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h5 id="3-1-2-Pipe"><a href="#3-1-2-Pipe" class="headerlink" title="3.1.2 Pipe()"></a>3.1.2 Pipe()</h5><p>Pipe的本质是进程之间的数据传递，而不是数据共享，这和socket有点像。pipe()返回两个连接对象分别表示管道的两端，每端都有send()和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么，这可能会损坏管道中的数据。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Pipe</span><br><span class="line"> </span><br><span class="line">def f(conn):</span><br><span class="line">    conn.send([42, None, &#39;hello&#39;])</span><br><span class="line">    conn.close()</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    parent_conn, child_conn &#x3D; Pipe() </span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(child_conn,))</span><br><span class="line">    p.start()</span><br><span class="line">    print(parent_conn.recv())   # prints &quot;[42, None, &#39;hello&#39;]&quot;</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h4 id="3-2-Manager"><a href="#3-2-Manager" class="headerlink" title="3.2 Manager"></a>3.2 Manager</h4><p>通过Manager可实现进程间数据的共享。Manager()返回的manager对象会通过一个服务进程，来使其他进程通过代理的方式操作python对象。manager对象支持 <code>list</code>, <code>dict</code>, <code>Namespace</code>, <code>Lock</code>, <code>RLock</code>, <code>Semaphore</code>, <code>BoundedSemaphore</code>, <code>Condition</code>, <code>Event</code>, <code>Barrier</code>, <code>Queue</code>, <code>Value</code> ,<code>Array</code>.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Manager</span><br><span class="line"> </span><br><span class="line">def f(d, l):</span><br><span class="line">    d[1] &#x3D; &#39;1&#39;</span><br><span class="line">    d[&#39;2&#39;] &#x3D; 2</span><br><span class="line">    d[0.25] &#x3D; None</span><br><span class="line">    l.append(1)</span><br><span class="line">    print(l)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    with Manager() as manager:</span><br><span class="line">        d &#x3D; manager.dict()</span><br><span class="line"> </span><br><span class="line">        l &#x3D; manager.list(range(5))</span><br><span class="line">        p_list &#x3D; []</span><br><span class="line">        for i in range(10):</span><br><span class="line">            p &#x3D; Process(target&#x3D;f, args&#x3D;(d, l))</span><br><span class="line">            p.start()</span><br><span class="line">            p_list.append(p)</span><br><span class="line">        for res in p_list:</span><br><span class="line">            res.join()</span><br><span class="line"> </span><br><span class="line">        print(d)</span><br><span class="line">        print(l)</span><br></pre></td></tr></table></figure>

<h4 id="3-3-进程锁（进程同步）"><a href="#3-3-进程锁（进程同步）" class="headerlink" title="3.3 进程锁（进程同步）"></a>3.3 进程锁（进程同步）</h4><p>数据输出的时候保证不同进程的输出内容在同一块屏幕正常显示，防止数据乱序的情况。<br>Without using the lock output from the different processes is liable to get all mixed up.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Lock</span><br><span class="line"> </span><br><span class="line">def f(l, i):</span><br><span class="line">    l.acquire()</span><br><span class="line">    try:</span><br><span class="line">        print(&#39;hello world&#39;, i)</span><br><span class="line">    finally:</span><br><span class="line">        l.release()</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    lock &#x3D; Lock()</span><br><span class="line"> </span><br><span class="line">    for num in range(10):</span><br><span class="line">        Process(target&#x3D;f, args&#x3D;(lock, num)).start()</span><br></pre></td></tr></table></figure>

<h4 id="3-4-进程池"><a href="#3-4-进程池" class="headerlink" title="3.4 进程池"></a>3.4 进程池</h4><p>由于进程启动的开销比较大，使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池，（由于启动线程的开销比较小，所以不需要线程池这种概念，多线程只会频繁得切换cpu导致系统变慢，并不会占用过多的内存空间）</p>
<p>进程池中常用方法：<br><code>apply()</code> 同步执行（串行）<br><code>apply_async()</code> 异步执行（并行）<br><code>terminate()</code> 立刻关闭进程池<br><code>join()</code> 主进程等待所有子进程执行完毕。必须在close或terminate()之后。<br><code>close()</code> 等待所有进程结束后，才关闭进程池。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">from  multiprocessing import Process,Pool</span><br><span class="line">import time</span><br><span class="line"> </span><br><span class="line">def Foo(i):</span><br><span class="line">    time.sleep(2)</span><br><span class="line">    return i+100</span><br><span class="line"> </span><br><span class="line">def Bar(arg):</span><br><span class="line">    print(&#39;--&gt;exec done:&#39;,arg)</span><br><span class="line"> </span><br><span class="line">pool &#x3D; Pool(5)  #允许进程池同时放入5个进程</span><br><span class="line"> </span><br><span class="line">for i in range(10):</span><br><span class="line">    pool.apply_async(func&#x3D;Foo, args&#x3D;(i,),callback&#x3D;Bar)  #func子进程执行完后，才会执行callback，否则callback不执行（而且callback是由父进程来执行了）</span><br><span class="line">    #pool.apply(func&#x3D;Foo, args&#x3D;(i,))</span><br><span class="line"> </span><br><span class="line">print(&#39;end&#39;)</span><br><span class="line">pool.close()</span><br><span class="line">pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。</span><br></pre></td></tr></table></figure>

<p>进程池内部维护一个进程序列，当使用时，去进程池中获取一个进程，如果进程池序列中没有可供使用的进程，那么程序就会等待，直到进程池中有可用进程为止。在上面的程序中产生了10个进程，但是只能有5同时被放入进程池，剩下的都被暂时挂起，并不占用内存空间，等前面的五个进程执行完后，再执行剩下5个进程。</p>
<h3 id="4-补充：协程"><a href="#4-补充：协程" class="headerlink" title="4 补充：协程"></a>4 补充：协程</h3><p>线程和进程的操作是由程序触发系统接口，最后的执行者是系统，它本质上是操作系统提供的功能。而协程的操作则是程序员指定的，在python中通过yield，人为的实现并发处理。</p>
<p>协程存在的意义：对于多线程应用，CPU通过切片的方式来切换线程间的执行，线程切换时需要耗时。协程，则只使用一个线程，分解一个线程成为多个“微线程”，在一个线程中规定某个代码块的执行顺序。</p>
<p>协程的适用场景：当程序中存在大量不需要CPU的操作时（IO）。<br>常用第三方模块gevent和greenlet。（本质上，gevent是对greenlet的高级封装，因此一般用它就行，这是一个相当高效的模块。）</p>
<h4 id="4-1-greenlet"><a href="#4-1-greenlet" class="headerlink" title="4.1 greenlet"></a>4.1 greenlet</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">from greenlet import greenlet</span><br><span class="line"></span><br><span class="line">def test1():</span><br><span class="line">    print(12)</span><br><span class="line">    gr2.switch()</span><br><span class="line">    print(34)</span><br><span class="line">    gr2.switch()</span><br><span class="line"></span><br><span class="line">def test2():</span><br><span class="line">    print(56)</span><br><span class="line">    gr1.switch()</span><br><span class="line">    print(78)</span><br><span class="line"></span><br><span class="line">gr1 &#x3D; greenlet(test1)</span><br><span class="line">gr2 &#x3D; greenlet(test2)</span><br><span class="line">gr1.switch()</span><br></pre></td></tr></table></figure>

<p>实际上，greenlet就是通过switch方法在不同的任务之间进行切换。</p>
<h4 id="4-2-gevent"><a href="#4-2-gevent" class="headerlink" title="4.2 gevent"></a>4.2 gevent</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">from gevent import monkey; monkey.patch_all()</span><br><span class="line">import gevent</span><br><span class="line">import requests</span><br><span class="line"></span><br><span class="line">def f(url):</span><br><span class="line">    print(&#39;GET: %s&#39; % url)</span><br><span class="line">    resp &#x3D; requests.get(url)</span><br><span class="line">    data &#x3D; resp.text</span><br><span class="line">    print(&#39;%d bytes received from %s.&#39; % (len(data), url))</span><br><span class="line"></span><br><span class="line">gevent.joinall([</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;www.python.org&#x2F;&#39;),</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;www.yahoo.com&#x2F;&#39;),</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;github.com&#x2F;&#39;),</span><br><span class="line">])</span><br></pre></td></tr></table></figure>

<p>通过joinall将任务f和它的参数进行统一调度，实现单线程中的协程。代码封装层次很高，实际使用只需要了解它的几个主要方法即可。</p>
<h2 id="调试代码"><a href="#调试代码" class="headerlink" title="调试代码"></a>调试代码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">#多进程1</span><br><span class="line">#方法一 直接调用import time</span><br><span class="line">import random</span><br><span class="line">from multiprocessing import Process</span><br><span class="line">def run(name):</span><br><span class="line">    print(&#39;%s runing&#39; %name)</span><br><span class="line">    time.sleep(random.randrange(1,5))</span><br><span class="line">    print(&#39;%s running end&#39; %name)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">p1&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;anne&#39;,)) #必须加,号 </span><br><span class="line">p2&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;alice&#39;,))</span><br><span class="line">p3&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;biantai&#39;,))</span><br><span class="line">p4&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;haha&#39;,))</span><br><span class="line"></span><br><span class="line">p1.start()</span><br><span class="line">p2.start()</span><br><span class="line">p3.start()</span><br><span class="line">p4.start()</span><br><span class="line">print(&#39;主线程&#39;)</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#多进程2</span><br><span class="line">#方法二 继承式调用</span><br><span class="line">import time</span><br><span class="line">import random</span><br><span class="line">from multiprocessing import Process</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class Run(Process):</span><br><span class="line">    def __init__(self,name):</span><br><span class="line">        super().__init__()</span><br><span class="line">        self.name&#x3D;name</span><br><span class="line">    def run(self):</span><br><span class="line">        print(&#39;%s runing&#39; %self.name)</span><br><span class="line">        time.sleep(random.randrange(1,5))</span><br><span class="line">        print(&#39;%s runing end&#39; %self.name)</span><br><span class="line"></span><br><span class="line">p1&#x3D;Run(&#39;anne&#39;)</span><br><span class="line">p2&#x3D;Run(&#39;alex&#39;)</span><br><span class="line">p3&#x3D;Run(&#39;ab&#39;)</span><br><span class="line">p4&#x3D;Run(&#39;hey&#39;)</span><br><span class="line">p1.start() #start会自动调用run</span><br><span class="line">p2.start()</span><br><span class="line">p3.start()</span><br><span class="line">p4.start()</span><br><span class="line">print(&#39;主线程&#39;)</span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/1-Django快速上手"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/1-Django%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/"
    >1-Django快速上手.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/1-Django%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="Django快速上手"><a href="#Django快速上手" class="headerlink" title="Django快速上手"></a>Django快速上手</h2><p>Web开发的早期阶段，开发者需要手动编写每个页面，例如一个新闻门户网站，每天都要修改它的HTML页面，随着网站规模和体量的增大，这种做法一定是非常糟糕的。为了解决这个问题，开发人员想到了用程序来为Web服务器生成动态内容，也就是说网页中的动态内容不再通过手动编写而是通过程序自动生成。最早的时候，这项技术被称为CGI（公共网关接口），当然随着时间的推移，CGI暴露出的问题也越来越多，例如大量重复的样板代码，总体性能较为低下等。在时代呼唤新英雄的背景下，PHP、ASP、JSP这类Web应用开发技术在上世纪90年代中后期如雨后春笋般涌现。通常我们说的Web应用是指通过浏览器来访问网络资源的应用程序，因为浏览器的普及性以及易用性，Web应用使用起来方便简单，免除了安装和更新应用程序带来的麻烦；站在开发者的角度，也不用关心用户使用什么样的操作系统，甚至不用区分是PC端还是移动端。</p>
<h3 id="Web应用机制和术语"><a href="#Web应用机制和术语" class="headerlink" title="Web应用机制和术语"></a>Web应用机制和术语</h3><p>下图向我们展示了Web应用的工作流程，其中涉及到的术语如下表所示。</p>
<p><img src="http://iubest.gitee.io/pic/web-application.png" alt="img"></p>
<blockquote>
<p>说明：相信有经验的读者会发现，这张图中其实还少了很多东西，例如反向代理服务器、数据库服务器、防火墙等，而且图中的每个节点在实际项目部署时可能是一组节点组成的集群。当然，如果你对这些没有什么概念也不要紧，继续下去就行了，后面会给大家一一讲解的。</p>
</blockquote>
<table>
<thead>
<tr>
<th>术语</th>
<th>解释</th>
</tr>
</thead>
<tbody><tr>
<td><strong>URL/URI</strong></td>
<td>统一资源定位符/统一资源标识符，网络资源的唯一标识</td>
</tr>
<tr>
<td><strong>域名</strong></td>
<td>与Web服务器地址对应的一个易于记忆的字符串名字</td>
</tr>
<tr>
<td><strong>DNS</strong></td>
<td>域名解析服务，可以将域名转换成对应的IP地址</td>
</tr>
<tr>
<td><strong>IP地址</strong></td>
<td>网络上的主机的身份标识，通过IP地址可以区分不同的主机</td>
</tr>
<tr>
<td><strong>HTTP</strong></td>
<td>超文本传输协议，构建在TCP之上的应用级协议，万维网数据通信的基础</td>
</tr>
<tr>
<td><strong>反向代理</strong></td>
<td>代理客户端向服务器发出请求，然后将服务器返回的资源返回给客户端</td>
</tr>
<tr>
<td><strong>Web服务器</strong></td>
<td>接受HTTP请求，然后返回HTML文件、纯文本文件、图像等资源给请求者</td>
</tr>
<tr>
<td><strong>Nginx</strong></td>
<td>高性能的Web服务器，也可以用作<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86">反向代理</a>，<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1">负载均衡</a> 和 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/HTTP%E7%BC%93%E5%AD%98">HTTP缓存</a></td>
</tr>
</tbody></table>
<h4 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h4><p>这里我们先费一些笔墨来说说HTTP这个协议。HTTP（超文本传输协议）是构建于TCP（传输控制协议）之上应用级协议，它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍，设计HTTP最初的目的是为了提供一种发布和接收<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/HTML">HTML</a>页面的方法，也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况，大家可以阅读<a target="_blank" rel="noopener" href="http://www.ruanyifeng.com/blog/2016/08/http.html">《HTTP 协议入门》</a>、<a target="_blank" rel="noopener" href="http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html">《互联网协议入门》</a>系列以及<a target="_blank" rel="noopener" href="http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html">《图解HTTPS协议》</a>这几篇文章进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal（抓包工具WireShark的前身）截取的访问百度首页时的HTTP请求和响应的报文（协议数据），由于Ethereal截取的是经过网络适配器的数据，因此可以清晰的看到从物理链路层到应用层的协议数据。</p>
<p>HTTP请求（请求行+请求头+空行+[消息体]）：</p>
<p><img src="http://iubest.gitee.io/pic/http-request.png" alt="img"></p>
<p>HTTP响应（响应行+响应头+空行+消息体）：</p>
<p><img src="http://iubest.gitee.io/pic/http-response.png" alt="img"></p>
<blockquote>
<p> <strong>说明</strong>：如果没有专业的抓包工具，也可以通过浏览器提供的“开发者工具”来查看HTTP请求和响应的数据格式。</p>
</blockquote>
<h3 id="Django概述"><a href="#Django概述" class="headerlink" title="Django概述"></a>Django概述</h3><p>Python的Web框架有上百个，比它的关键字还要多。所谓Web框架，就是用于开发Web服务器端应用的基础设施，说得通俗一点就是一系列封装好的模块和工具。事实上，即便没有Web框架，我们仍然可以通过socket或<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3">CGI</a>来开发Web服务器端应用，但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架，我们可以化繁为简，降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架，这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。</p>
<p>在上述Python的Web框架中，Django无疑是最有代表性的重量级选手，开发者可以基于Django快速的开发可靠的Web应用程序，因为它减少了Web开发中不必要的开销，对常用的设计和开发模式进行了封装，并对MVC架构提供了支持（Django中称之为MTV架构）。MVC是软件系统开发领域中一种放之四海而皆准的架构，它将系统中的组件分为模型（Model）、视图（View）和控制器（Controller）三个部分并借此实现模型（数据）和视图（显示）的解耦合。由于模型和视图进行了分离，所以需要一个中间人将解耦合的模型和视图联系起来，扮演这个角色的就是控制器。稍具规模的软件系统都会使用MVC架构（或者是从MVC演进出的其他架构），Django项目中我们称之为MTV，MTV中的M跟MVC中的M没有区别，就是代表数据的模型，T代表了网页模板（显示数据的视图），而V代表了视图函数，在Django框架中，视图函数和Django框架本身一起扮演了MVC中C的角色。</p>
<p><img src="http://iubest.gitee.io/pic/mvc.png" alt="img"></p>
<p>Django框架诞生于2003年，它是一个在真正的应用中成长起来的项目，由劳伦斯出版集团旗下在线新闻网站的内容管理系统（CMS）研发团队（主要是Adrian Holovaty和Simon Willison）开发，以比利时的吉普赛爵士吉他手Django Reinhardt来命名。Django框架在2005年夏天作为开源框架发布，使用Django框架能用很短的时间构建出功能完备的网站，因为它代替程序员完成了那些重复乏味的劳动，剩下真正有意义的核心业务给程序员来开发，这一点就是对DRY（Don’t Repeat Yourself）理念的最好践行。许多成功的网站和应用都是基于Python语言进行开发的，国内比较有代表性的网站包括：知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等，其中不乏使用了Django框架的产品。</p>
<h3 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h3><h4 id="第一个Django项目"><a href="#第一个Django项目" class="headerlink" title="第一个Django项目"></a>第一个Django项目</h4><ol>
<li><p>检查Python环境：Django 1.11需要Python 2.7或Python 3.4以上的版本；Django 2.0需要Python 3.4以上的版本；Django 2.1和2.2需要Python 3.5以上的版本；Django 3.0需要Python 3.6以上版本。</p>
<blockquote>
<p><strong>说明</strong>：Django框架不同版本所需的Python解释器环境，可以在Django官方文档的<a target="_blank" rel="noopener" href="https://docs.djangoproject.com/zh-hans/3.0/faq/install/#faq-python-version-support">FAQ</a>中找到。</p>
</blockquote>
<p>可以在macOS的终端中输入下面的命令检查Python解释器版本，Windows系统可以在命令行提示符中输入<code>python --version</code>。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 --version</span><br></pre></td></tr></table></figure>

<p>也可以在Python的交互式环境中执行下面的代码来查看Python解释器的版本。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import sys</span><br><span class="line">sys.version</span><br><span class="line">sys.version_info</span><br></pre></td></tr></table></figure>
</li>
<li><p>更新包管理工具并安装Django环境（用于创建Django项目）。</p>
<blockquote>
<p><strong>说明</strong>：在更新这个文档时，Django最新的正式版本是3.0.7，Django 3.0提供了对ASGI的支持，可以实现全双工的异步通信，但是目前的使用体验一般，所以暂时不推荐大家使用Django 3.0，下面我们安装的是Django 2.2.13版本。使用<code>pip</code>安装三方库和工具时，可以通过<code>==</code>来指定安装的版本。</p>
</blockquote>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip3 install -U pip</span><br><span class="line">pip3 install django==2.2.13</span><br></pre></td></tr></table></figure>
</li>
<li><p>检查Django环境并使用<code>django-admin</code>命令创建Django项目（项目名称为hellodjango）。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">django-admin --version</span><br><span class="line">django-admin startproject hellodjango</span><br></pre></td></tr></table></figure>
</li>
<li><p>用PyCharm打开创建好的Djang项目，并为其添加虚拟环境。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-django-project.png" alt="img"></p>
<p>如上图所示，PyCharm的项目浏览器中，最顶层的文件夹<code>hellodjango</code>是Python项目文件夹，这个文件夹的名字并不重要，Django项目也不关心这个文件夹叫什么名字。该文件夹下有一个同名的文件夹，它是Django项目文件夹，其中包含了<code>__init__.py</code>、<code>settings.py</code>、<code>urls.py</code>、<code>wsgi.py</code>四个文件，与名为<code>hellodjango</code>的Django项目文件夹同级的还有一个名为<code>manage.py</code> 的文件，这些文件的作用如下所示：</p>
<ul>
<li><code>hellodjango/__init__.py</code>：空文件，告诉Python解释器这个目录应该被视为一个Python的包。</li>
<li><code>hellodjango/settings.py</code>：Django项目的配置文件。</li>
<li><code>hellodjango/urls.py</code>：Django项目的URL映射声明，就像是网站的“目录”。</li>
<li><code>hellodjango/wsgi.py</code>：项目运行在WSGI兼容Web服务器上的入口文件。</li>
<li><code>manage.py</code>： 管理Django项目的脚本程序。</li>
</ul>
<blockquote>
<p>说明：WSGI全称是Web服务器网关接口，维基百科上给出的解释是“为Python语言定义的<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8">Web服务器</a>和<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F">Web应用程序</a>或框架之间的一种简单而通用的接口”。</p>
</blockquote>
<p>创建虚拟环境的界面如下图所示。</p>
<p><img src="res/pycharm-django-virtual-environment.png" alt="pycharm-django-virtual-environment"></p>
</li>
<li><p>安装项目依赖项。</p>
<p>方法一：打开PyCharm的终端，在终端中通过<code>pip</code>命令安装Django项目的依赖项。</p>
<blockquote>
<p><strong>说明</strong>：由于已经基于Python 3解释器环境为项目创建了虚拟环境，所以虚拟环境中的<code>python</code>命令对应的是Python 3的解释器，而<code>pip</code>命令对应的是Python 3的包管理工具。</p>
</blockquote>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install django==2.2.13</span><br></pre></td></tr></table></figure>

<p>方法二：在PyCharm的偏好设置中，可以找到项目的解释器环境和已经安装的三方库，可以通过点击添加按钮来安装新的依赖项，需要提醒大家的是在安装Django依赖项时，需要指定版本号，否则将默认安装更新本文时最新的3.0.7版本。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-install-django.png" alt="img"></p>
<p>下图展示了Django版本和Python版本的对应关系，请大家自行对号入座。</p>
<table>
<thead>
<tr>
<th>Django版本</th>
<th>Python版本</th>
</tr>
</thead>
<tbody><tr>
<td>1.8</td>
<td>2.7、3.2、3.3、3.4、3.5</td>
</tr>
<tr>
<td>1.9、1.10</td>
<td>2.7、3.4、3.5</td>
</tr>
<tr>
<td>1.11</td>
<td>2.7、3.4、3.5、3.6、3.7（Django 1.11.17）</td>
</tr>
<tr>
<td>2.0</td>
<td>3.4、3.5、3.6、3.7</td>
</tr>
<tr>
<td>2.1</td>
<td>3.5、3.6、3.7</td>
</tr>
<tr>
<td>2.2</td>
<td>3.5、3.6、3.7、3.8（Django 2.2.8）</td>
</tr>
<tr>
<td>3.0</td>
<td>3.6、3.7、3.8</td>
</tr>
</tbody></table>
</li>
<li><p>启动Django自带的服务器运行项目。</p>
<p>方法一：在“Run”菜单选择“Edit Configuration”，配置“Django server”运行项目（适用于专业版PyCharm）。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-django-server.png" alt="img"></p>
<p>方法二：在“Run”菜单选择“Edit Configuration”，配置运行“Python”程序运行项目（适用于专业版和社区版PyCharm）。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-python-manage.png" alt="img"></p>
<p>方法三：在PyCharm的终端（Terminal）中通过命令运行项目（适用于专业版和社区版PyCharm）。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py runserver</span><br></pre></td></tr></table></figure>
</li>
<li><p>查看运行效果。</p>
<p>在浏览器中输入<code>http://127.0.0.1:8000</code>访问我们的服务器，效果如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/django-index-1.png" alt="img"></p>
<blockquote>
<p><strong>说明</strong>：</p>
<ol>
<li>刚刚启动的Django自带的服务器只能用于开发和测试环境，因为这个服务器是纯Python编写的轻量级Web服务器，不适合在生产环境中使用。</li>
<li>如果修改了代码，不需要为了让修改的代码生效而重新启动Django自带的服务器。但是，在添加新的项目文件时，该服务器不会自动重新加载，这个时候就得手动重启服务器。</li>
<li>可以在终端中通过<code>python manage.py help</code>命令查看Django管理脚本程序可用的命令参数。</li>
<li>使用<code>python manage.py runserver</code>启动服务器时，可以在后面添加参数来指定IP地址和端口号，默认情况下启动的服务器将运行在本机的<code>8000</code>端口。</li>
<li>在终端中运行的服务器，可以通过Ctrl+C来停止它 。通过PyCharm的“运行配置”运行的服务器直接点击窗口上的关闭按钮就可以终止服务器的运行。</li>
<li>不能在同一个端口上启动多个服务器，因为会导致地址的冲突（端口是对IP地址的扩展，也是计算机网络地址的一部分）。</li>
</ol>
</blockquote>
</li>
<li><p>修改项目的配置文件<code>settings.py</code>。</p>
<p>Django是一个支持国际化和本地化的框架，因此刚才我们看到的Django项目的默认首页也是支持国际化的，我们可以通过修改配置文件将默认语言修改为中文，时区设置为东八区。</p>
<p>找到修改前的配置（在<code>settings.py</code>文件第100行以后）。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">LANGUAGE_CODE = <span class="string">&#x27;en-us&#x27;</span></span><br><span class="line">TIME_ZONE = <span class="string">&#x27;UTC&#x27;</span></span><br></pre></td></tr></table></figure>

<p>修改为以下内容。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">LANGUAGE_CODE = <span class="string">&#x27;zh-hans&#x27;</span></span><br><span class="line">TIME_ZONE = <span class="string">&#x27;Asia/Chongqing&#x27;</span></span><br></pre></td></tr></table></figure>

<p>刷新刚才的页面，可以看到修改语言代码和时区之后的结果。</p>
<p><img src="http://iubest.gitee.io/pic/django-index-2.png" alt="img"></p>
</li>
</ol>
<h4 id="创建自己的应用"><a href="#创建自己的应用" class="headerlink" title="创建自己的应用"></a>创建自己的应用</h4><p>如果要开发自己的Web应用，需要先在Django项目中创建“应用”，一个Django项目可以包含一个或多个应用。</p>
<ol>
<li><p>在PyCharm的终端中执行下面的命令，创建名为<code>first</code>的应用。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py startapp first</span><br></pre></td></tr></table></figure>

<p>执行上面的命令会在当前路径下创建<code>first</code>目录，其目录结构如下所示：</p>
<ul>
<li><code>__init__.py</code>：一个空文件，告诉Python解释器这个目录应该被视为一个Python的包。</li>
<li><code>admin.py</code>：可以用来注册模型，用于在Django框架自带的管理后台中管理模型。</li>
<li><code>apps.py</code>：当前应用的配置文件。</li>
<li><code>migrations</code>：存放与模型有关的数据库迁移信息。<ul>
<li><code>__init__.py</code>：一个空文件，告诉Python解释器这个目录应该被视为一个Python的包。</li>
</ul>
</li>
<li><code>models.py</code>：存放应用的数据模型（MTV中的M）。</li>
<li><code>tests.py</code>：包含测试应用各项功能的测试类和测试函数。</li>
<li><code>views.py</code>：处理用户HTTP请求并返回HTTP响应的函数或类（MTV中的V）。</li>
</ul>
</li>
<li><p>修改应用目录下的视图文件<code>views.py</code>。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.http <span class="keyword">import</span> HttpResponse</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_index</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="keyword">return</span> HttpResponse(<span class="string">&#x27;&lt;h1&gt;Hello, Django!&lt;/h1&gt;&#x27;</span>)</span><br></pre></td></tr></table></figure>
</li>
<li><p>修改Django项目目录下的<code>urls.py</code>文件，将视图函数和用户在浏览器中请求的路径对应。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path, include</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> first.views <span class="keyword">import</span> show_index</span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line">    path(<span class="string">&#x27;admin/&#x27;</span>, admin.site.urls),</span><br><span class="line">    path(<span class="string">&#x27;hello/&#x27;</span>, show_index),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
</li>
<li><p>重新运行项目，并打开浏览器中访问<code>http://127.0.0.1:8000/hello/</code>。</p>
</li>
<li><p>上面我们通过代码为浏览器生成了内容，但仍然是静态内容，如果要生成动态内容，可以修改<code>views.py</code>文件并添加如下所示的代码。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> random <span class="keyword">import</span> sample</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.http <span class="keyword">import</span> HttpResponse</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_index</span>(<span class="params">request</span>):</span></span><br><span class="line">    fruits = [</span><br><span class="line">        <span class="string">&#x27;Apple&#x27;</span>, <span class="string">&#x27;Orange&#x27;</span>, <span class="string">&#x27;Pitaya&#x27;</span>, <span class="string">&#x27;Durian&#x27;</span>, <span class="string">&#x27;Waxberry&#x27;</span>, <span class="string">&#x27;Blueberry&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Grape&#x27;</span>, <span class="string">&#x27;Peach&#x27;</span>, <span class="string">&#x27;Pear&#x27;</span>, <span class="string">&#x27;Banana&#x27;</span>, <span class="string">&#x27;Watermelon&#x27;</span>, <span class="string">&#x27;Mango&#x27;</span></span><br><span class="line">    ]</span><br><span class="line">    selected_fruits = sample(fruits, <span class="number">3</span>)</span><br><span class="line">    content = <span class="string">&#x27;&lt;h3&gt;今天推荐的水果是：&lt;/h3&gt;&#x27;</span></span><br><span class="line">    content += <span class="string">&#x27;&lt;hr&gt;&#x27;</span></span><br><span class="line">    content += <span class="string">&#x27;&lt;ul&gt;&#x27;</span></span><br><span class="line">    <span class="keyword">for</span> fruit <span class="keyword">in</span> selected_fruits:</span><br><span class="line">        content += <span class="string">f&#x27;&lt;li&gt;<span class="subst">&#123;fruit&#125;</span>&lt;/li&gt;&#x27;</span></span><br><span class="line">    content += <span class="string">&#x27;&lt;/ul&gt;&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> HttpResponse(content)</span><br></pre></td></tr></table></figure>
</li>
<li><p>刷新页面查看程序的运行结果，看看每次刷新的网页的时候，是不是可以看到不一样的内容。</p>
</li>
</ol>
<h4 id="使用模板"><a href="#使用模板" class="headerlink" title="使用模板"></a>使用模板</h4><p>上面通过拼接HTML代码的方式为浏览器生成动态内容的做法在实际开发中是无能接受的，因为实际项目中的前端页面可能非常复杂，无法用这种拼接动态内容的方式来完成，这一点大家一定能够想到。为了解决这个问题，我们可以提前准备一个模板页（MTV中的T），所谓模板页就是一个带占位符和模板指令的HTML页面。</p>
<p>Django框架中有一个名为<code>render</code>的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的模板指令和占位符，当然这里的渲染称为后端渲染，即在服务器端完成页面的渲染再输出到浏览器中。后端渲染的做法在Web应用的访问量较大时，会让服务器承受较大的负担，所以越来越多的Web应用会选择前端渲染的方式，即服务器只提供页面所需的数据（通常是JSON格式），在浏览器中通过JavaScript代码获取这些数据并渲染页面上。关于前端渲染的内容，我们会在后续的课程中为大家讲解，目前我们使用的是通过模板页进行后端渲染的做法，具体步骤如下所示。</p>
<p>使用模板页的步骤如下所示。</p>
<ol>
<li><p>在项目目录下创建名为templates文件夹。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-django-template.png" alt="img"></p>
</li>
<li><p>添加模板页<code>index.html</code>。</p>
<blockquote>
<p><strong>说明</strong>：实际项目开发中，静态页由前端开发者提供，后端开发者需要将静态页修改为模板页，以便通过Python程序对其进行渲染，这种做法就是上面提到的后端渲染。</p>
</blockquote>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">title</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">            <span class="selector-id">#fruits</span> &#123;</span></span><br><span class="line"><span class="css">                <span class="selector-tag">font-size</span>: 1<span class="selector-class">.25em</span>;</span></span><br><span class="line">            &#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>今天推荐的水果是：<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;fruits&quot;</span>&gt;</span></span><br><span class="line">            &#123;% for fruit in fruits %&#125;</span><br><span class="line">            <span class="tag">&lt;<span class="name">li</span>&gt;</span>&#123;&#123; fruit &#125;&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">            &#123;% endfor %&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>在上面的模板页中我们使用了<code>&#123;&#123; fruit &#125;&#125;</code>这样的模板占位符语法，也使用了<code>&#123;% for %&#125;</code>这样的模板指令，这些都是Django模板语言（DTL）的一部分。关于模板语法和指令，大家可以看看官方文档，相信这些内容还是很容易理解的，并不需要过多的赘述，大家也可以参考<a href="">官方文档</a>了解模板指令和语法。</p>
</li>
<li><p>修改<code>views.py</code>文件，调用<code>render</code>函数渲染模板页。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> random <span class="keyword">import</span> sample</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.shortcuts <span class="keyword">import</span> render</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_index</span>(<span class="params">request</span>):</span></span><br><span class="line">    fruits = [</span><br><span class="line">        <span class="string">&#x27;Apple&#x27;</span>, <span class="string">&#x27;Orange&#x27;</span>, <span class="string">&#x27;Pitaya&#x27;</span>, <span class="string">&#x27;Durian&#x27;</span>, <span class="string">&#x27;Waxberry&#x27;</span>, <span class="string">&#x27;Blueberry&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;Grape&#x27;</span>, <span class="string">&#x27;Peach&#x27;</span>, <span class="string">&#x27;Pear&#x27;</span>, <span class="string">&#x27;Banana&#x27;</span>, <span class="string">&#x27;Watermelon&#x27;</span>, <span class="string">&#x27;Mango&#x27;</span></span><br><span class="line">    ]</span><br><span class="line">    selected_fruits = sample(fruits, <span class="number">3</span>)</span><br><span class="line">    <span class="keyword">return</span> render(request, <span class="string">&#x27;index.html&#x27;</span>, &#123;<span class="string">&#x27;fruits&#x27;</span>: selected_fruits&#125;)</span><br></pre></td></tr></table></figure>

<p><code>render</code>函数的第一个参数是请求对象request，第二个参数是我们要渲染的模板页的名字，第三个参数是要渲染到页面上的数据，我们通过一个字典将数据交给模板页，字典中的键就是模板页中使用的模板指令或占位符中的变量名。</p>
</li>
<li><p>到此为止，视图函数中的<code>render</code>还无法找到模板文件<code>index.html</code>，需要修改<code>settings.py</code>文件，配置模板文件所在的路径。修改<code>settings.py</code>文件，找到<code>TEMPLATES</code>配置，修改其中的<code>DIRS</code>配置。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">TEMPLATES = [</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="string">&#x27;BACKEND&#x27;</span>: <span class="string">&#x27;django.template.backends.django.DjangoTemplates&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;DIRS&#x27;</span>: [os.path.join(BASE_DIR, <span class="string">&#x27;templates&#x27;</span>), ],</span><br><span class="line">        <span class="string">&#x27;APP_DIRS&#x27;</span>: <span class="literal">True</span>,</span><br><span class="line">        <span class="string">&#x27;OPTIONS&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;context_processors&#x27;</span>: [</span><br><span class="line">                <span class="string">&#x27;django.template.context_processors.debug&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;django.template.context_processors.request&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;django.contrib.auth.context_processors.auth&#x27;</span>,</span><br><span class="line">                <span class="string">&#x27;django.contrib.messages.context_processors.messages&#x27;</span>,</span><br><span class="line">            ],</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>
</li>
<li><p>重新运行项目或直接刷新页面查看结果。</p>
</li>
</ol>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>至此，我们已经利用Django框架完成了一个非常小的Web应用，虽然它并没有任何的实际价值，但是可以通过这个项目对Django框架有一个感性的认识。学习Django最好的资料肯定是它的<a target="_blank" rel="noopener" href="https://docs.djangoproject.com/zh-hans/2.0/">官方文档</a>，官方文档提供了对多国语言的支持，而且有新手教程引导初学者学习使用Django框架，建议大家通过阅读Django的官方文档来学习和使用这个框架。当然图灵社区出版的<a target="_blank" rel="noopener" href="http://www.ituring.com.cn/book/2630">《Django基础教程》</a>也是非常适合初学者的入门级读物，有兴趣的读者可以点击链接进行购买。 </p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/2-深入模型"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/2-%E6%B7%B1%E5%85%A5%E6%A8%A1%E5%9E%8B/"
    >2-深入模型.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/2-%E6%B7%B1%E5%85%A5%E6%A8%A1%E5%9E%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="深入模型"><a href="#深入模型" class="headerlink" title="深入模型"></a>深入模型</h2><p>在上一个章节中，我们提到了Django是基于MVC架构的Web框架，MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据（的表示），所以通常也被称作“数据模型”。在实际的项目中，数据模型通常通过数据库实现持久化操作，而关系型数据库在过去和当下都是持久化的首选方案，下面我们通过完成一个投票项目来讲解和模型相关的知识点。投票项目的首页会展示某在线教育平台所有的学科；点击学科可以查看到该学科的老师及其信息；用户登录后在查看老师的页面为老师投票，可以投赞成票和反对票；未登录的用户可以通过登录页进行登录；尚未注册的用户可以通过注册页输入个人信息进行注册。在这个项目中，我们使用MySQL数据库来实现数据持久化操作。</p>
<h3 id="创建项目和应用"><a href="#创建项目和应用" class="headerlink" title="创建项目和应用"></a>创建项目和应用</h3><p>我们首先创建Django项目<code>vote</code>并为其添加虚拟环境和依赖项。接下来，在项目下创建名为<code>polls</code>的应用和保存模板页的文件夹<code>tempaltes</code>，项目文件夹的结构如下所示。</p>
<p><img src="http://iubest.gitee.io/pic/pycharm-vote-project.png" alt="img"></p>
<p>根据上面描述的项目需求，我们准备了四个静态页面，分别是展示学科的页面<code>subjects.html</code>，显示学科老师的页面<code>teachers.html</code>，登录页面<code>login.html</code>，注册页面<code>register.html</code>，稍后我们会将静态页修改为Django项目所需的模板页。</p>
<h3 id="配置关系型数据库MySQL"><a href="#配置关系型数据库MySQL" class="headerlink" title="配置关系型数据库MySQL"></a>配置关系型数据库MySQL</h3><ol>
<li><p>在MySQL中创建数据库，创建用户，授权用户访问该数据库。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">database</span> vote <span class="keyword">default</span> <span class="keyword">charset</span> utf8;</span><br><span class="line"><span class="keyword">create</span> <span class="keyword">user</span> <span class="string">&#x27;hellokitty&#x27;</span>@<span class="string">&#x27;%&#x27;</span> <span class="keyword">identified</span> <span class="keyword">by</span> <span class="string">&#x27;Hellokitty.618&#x27;</span>;</span><br><span class="line"><span class="keyword">grant</span> <span class="keyword">all</span> <span class="keyword">privileges</span> <span class="keyword">on</span> vote.* <span class="keyword">to</span> <span class="string">&#x27;hellokitty&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line"><span class="keyword">flush</span> <span class="keyword">privileges</span>;</span><br></pre></td></tr></table></figure>
</li>
<li><p>在MySQL中创建保存学科和老师信息的二维表（保存用户信息的表稍后处理）。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> vote;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 创建学科表</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> <span class="string">`tb_subject`</span></span><br><span class="line">(</span><br><span class="line">	<span class="string">`no`</span> <span class="built_in">integer</span> auto_increment <span class="keyword">comment</span> <span class="string">&#x27;学科编号&#x27;</span>,</span><br><span class="line">    <span class="string">`name`</span> <span class="built_in">varchar</span>(<span class="number">50</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">&#x27;学科名称&#x27;</span>,</span><br><span class="line">    <span class="string">`intro`</span> <span class="built_in">varchar</span>(<span class="number">1000</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="string">&#x27;&#x27;</span> <span class="keyword">comment</span> <span class="string">&#x27;学科介绍&#x27;</span>,</span><br><span class="line">    <span class="string">`is_hot`</span> <span class="built_in">boolean</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="number">0</span> <span class="keyword">comment</span> <span class="string">&#x27;是不是热门学科&#x27;</span>,</span><br><span class="line">    primary <span class="keyword">key</span> (<span class="string">`no`</span>)</span><br><span class="line">);</span><br><span class="line"><span class="comment">-- 创建老师表</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> <span class="string">`tb_teacher`</span></span><br><span class="line">(</span><br><span class="line">    <span class="string">`no`</span> <span class="built_in">integer</span> auto_increment <span class="keyword">comment</span> <span class="string">&#x27;老师编号&#x27;</span>,</span><br><span class="line">    <span class="string">`name`</span> <span class="built_in">varchar</span>(<span class="number">20</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">&#x27;老师姓名&#x27;</span>,</span><br><span class="line">    <span class="string">`sex`</span> <span class="built_in">boolean</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="number">1</span> <span class="keyword">comment</span> <span class="string">&#x27;老师性别&#x27;</span>,</span><br><span class="line">    <span class="string">`birth`</span> <span class="built_in">date</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">&#x27;出生日期&#x27;</span>,</span><br><span class="line">    <span class="string">`intro`</span> <span class="built_in">varchar</span>(<span class="number">1000</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="string">&#x27;&#x27;</span> <span class="keyword">comment</span> <span class="string">&#x27;老师介绍&#x27;</span>,</span><br><span class="line">    <span class="string">`photo`</span> <span class="built_in">varchar</span>(<span class="number">255</span>) <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="string">&#x27;&#x27;</span> <span class="keyword">comment</span> <span class="string">&#x27;老师照片&#x27;</span>,</span><br><span class="line">    <span class="string">`gcount`</span> <span class="built_in">integer</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="number">0</span> <span class="keyword">comment</span> <span class="string">&#x27;好评数&#x27;</span>,</span><br><span class="line">    <span class="string">`bcount`</span> <span class="built_in">integer</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">default</span> <span class="number">0</span> <span class="keyword">comment</span> <span class="string">&#x27;差评数&#x27;</span>,</span><br><span class="line">    <span class="string">`sno`</span> <span class="built_in">integer</span> <span class="keyword">not</span> <span class="literal">null</span> <span class="keyword">comment</span> <span class="string">&#x27;所属学科&#x27;</span>,</span><br><span class="line">    primary <span class="keyword">key</span> (<span class="string">`no`</span>),</span><br><span class="line">    <span class="keyword">foreign</span> <span class="keyword">key</span> (<span class="string">`sno`</span>) <span class="keyword">references</span> <span class="string">`tb_subject`</span> (<span class="string">`no`</span>)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
</li>
<li><p>在虚拟环境中安装连接MySQL数据库所需的依赖项。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install mysqlclient</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：如果因为某些原因无法安装<code>mysqlclient</code>三方库，可以使用它的替代品<code>pymysql</code>，<code>pymysql</code>是用纯Python开发的连接MySQL的Python库，安装更容易成功，但是需要在Django项目文件夹的<code>__init__.py</code>中添加如下所示的代码。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pymysql</span><br><span class="line"></span><br><span class="line">pymysql.install_as_MySQLdb()</span><br></pre></td></tr></table></figure>

<p>如果使用Django 2.2及以上版本，还会遇到PyMySQL跟Django框架的兼容性问题，兼容性问题会导致项目无法运行，需要按照GitHub上PyMySQL仓库<a target="_blank" rel="noopener" href="https://github.com/PyMySQL/PyMySQL/issues/790">Issues</a>中提供的方法进行处理。总体来说，使用<code>pymysql</code>会比较麻烦，强烈建议大家首选安装<code>mysqlclient</code>。</p>
</blockquote>
</li>
<li><p>修改项目的settings.py文件，首先将我们创建的应用<code>polls</code>添加已安装的项目（<code>INSTALLED_APPS</code>）中，然后配置MySQL作为持久化方案。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS = [</span><br><span class="line">    <span class="string">&#x27;django.contrib.admin&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.auth&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.contenttypes&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.sessions&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.messages&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;django.contrib.staticfiles&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;polls&#x27;</span>,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">DATABASES = &#123;</span><br><span class="line">    <span class="string">&#x27;default&#x27;</span>: &#123;</span><br><span class="line">        <span class="comment"># 数据库引擎配置</span></span><br><span class="line">        <span class="string">&#x27;ENGINE&#x27;</span>: <span class="string">&#x27;django.db.backends.mysql&#x27;</span>,</span><br><span class="line">        <span class="comment"># 数据库的名字</span></span><br><span class="line">        <span class="string">&#x27;NAME&#x27;</span>: <span class="string">&#x27;vote&#x27;</span>,</span><br><span class="line">        <span class="comment"># 数据库服务器的IP地址（本机可以写localhost或127.0.0.1）</span></span><br><span class="line">        <span class="string">&#x27;HOST&#x27;</span>: <span class="string">&#x27;localhost&#x27;</span>,</span><br><span class="line">        <span class="comment"># 启动MySQL服务的端口号</span></span><br><span class="line">        <span class="string">&#x27;PORT&#x27;</span>: <span class="number">3306</span>,</span><br><span class="line">        <span class="comment"># 数据库用户名和口令</span></span><br><span class="line">        <span class="string">&#x27;USER&#x27;</span>: <span class="string">&#x27;hellokitty&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;PASSWORD&#x27;</span>: <span class="string">&#x27;Hellokitty.618&#x27;</span>,</span><br><span class="line">        <span class="comment"># 数据库使用的字符集</span></span><br><span class="line">        <span class="string">&#x27;CHARSET&#x27;</span>: <span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">        <span class="comment"># 数据库时间日期的时区设定</span></span><br><span class="line">        <span class="string">&#x27;TIME_ZONE&#x27;</span>: <span class="string">&#x27;Asia/Chongqing&#x27;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在配置ENGINE属性时，常用的可选值包括：</p>
<ul>
<li><code>&#39;django.db.backends.sqlite3&#39;</code>：SQLite嵌入式数据库。</li>
<li><code>&#39;django.db.backends.postgresql&#39;</code>：BSD许可证下发行的开源关系型数据库产品。</li>
<li><code>&#39;django.db.backends.mysql&#39;</code>：甲骨文公司经济高效的数据库产品。</li>
<li><code>&#39;django.db.backends.oracle&#39;</code>：甲骨文公司关系型数据库旗舰产品。</li>
</ul>
<p>其他的配置可以参考官方文档中<a target="_blank" rel="noopener" href="https://docs.djangoproject.com/zh-hans/2.0/ref/databases/#third-party-notes">数据库配置</a>的部分。</p>
</li>
<li><p>Django框架提供了ORM来解决数据持久化问题，ORM翻译成中文叫“对象关系映射”。因为Python是面向对象的编程语言，我们在Python程序中使用对象模型来保存数据，而关系型数据库使用关系模型，用二维表来保存数据，这两种模型并不匹配。使用ORM是为了实现对象模型到关系模型的<strong>双向转换</strong>，这样就不用在Python代码中书写SQL语句和游标操作，因为这些都会由ORM自动完成。利用Django的ORM，我们可以直接将刚才创建的学科表和老师表变成Django中的模型类。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py inspectdb &gt; polls/models.py</span><br></pre></td></tr></table></figure>

<p>我们可以对自动生成的模型类稍作调整，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Subject</span>(<span class="params">models.Model</span>):</span></span><br><span class="line">    no = models.AutoField(primary_key=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;编号&#x27;</span>)</span><br><span class="line">    name = models.CharField(max_length=<span class="number">50</span>, verbose_name=<span class="string">&#x27;名称&#x27;</span>)</span><br><span class="line">    intro = models.CharField(max_length=<span class="number">1000</span>, verbose_name=<span class="string">&#x27;介绍&#x27;</span>)</span><br><span class="line">    is_hot = models.BooleanField(verbose_name=<span class="string">&#x27;是否热门&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        managed = <span class="literal">False</span></span><br><span class="line">        db_table = <span class="string">&#x27;tb_subject&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Teacher</span>(<span class="params">models.Model</span>):</span></span><br><span class="line">    no = models.AutoField(primary_key=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;编号&#x27;</span>)</span><br><span class="line">    name = models.CharField(max_length=<span class="number">20</span>, verbose_name=<span class="string">&#x27;姓名&#x27;</span>)</span><br><span class="line">    sex = models.BooleanField(default=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;性别&#x27;</span>)</span><br><span class="line">    birth = models.DateField(verbose_name=<span class="string">&#x27;出生日期&#x27;</span>)</span><br><span class="line">    intro = models.CharField(max_length=<span class="number">1000</span>, verbose_name=<span class="string">&#x27;个人介绍&#x27;</span>)</span><br><span class="line">    photo = models.ImageField(max_length=<span class="number">255</span>, verbose_name=<span class="string">&#x27;照片&#x27;</span>)</span><br><span class="line">    good_count = models.IntegerField(default=<span class="number">0</span>, db_column=<span class="string">&#x27;gcount&#x27;</span>, verbose_name=<span class="string">&#x27;好评数&#x27;</span>)</span><br><span class="line">    bad_count = models.IntegerField(default=<span class="number">0</span>, db_column=<span class="string">&#x27;bcount&#x27;</span>, verbose_name=<span class="string">&#x27;差评数&#x27;</span>)</span><br><span class="line">    subject = models.ForeignKey(Subject, models.DO_NOTHING, db_column=<span class="string">&#x27;sno&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        managed = <span class="literal">False</span></span><br><span class="line">        db_table = <span class="string">&#x27;tb_teacher&#x27;</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：模型类都直接或间接继承自<code>Model</code>类，模型类跟关系型数据库的二维表对应，模型对象跟表中的记录对应，模型对象的属性跟表中的字段对应。如果对上面模型类的属性定义不是特别理解，可以看看本文后面提供的“模型定义参考”部分的内容。</p>
<p><strong>managed属性说明：</strong></p>
<p>由于Django会自动根据模型类生成映射的数据库表，如果你不希望Django这么做，可以把managed的值设置为False。</p>
<p>默认值为True,这个选项为True时Django可以对数据库表进行 migrate或migrations、删除等操作。在这个时间Django将管理数据库中表的生命周期</p>
<p>如果为False的时候，不会对数据库表进行创建、删除等操作。可以用于现有表、数据库视图等，其他操作是一样的。</p>
</blockquote>
</li>
</ol>
<h3 id="使用ORM完成模型的CRUD操作"><a href="#使用ORM完成模型的CRUD操作" class="headerlink" title="使用ORM完成模型的CRUD操作"></a>使用ORM完成模型的CRUD操作</h3><p>有了Django框架的ORM，我们可以直接使用面向对象的方式来实现对数据的CRUD（增删改查）操作。我们可以在PyCharm的终端中输入下面的命令进入到Django项目的交互式环境，然后尝试对模型的操作。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py shell</span><br></pre></td></tr></table></figure>

<h4 id="新增"><a href="#新增" class="headerlink" title="新增"></a>新增</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> polls.models <span class="keyword">import</span> Subject</span><br><span class="line"></span><br><span class="line">subject1 = Subject(name=<span class="string">&#x27;Python全栈开发&#x27;</span>, intro=<span class="string">&#x27;当下最热门的学科&#x27;</span>, is_hot=<span class="literal">True</span>)</span><br><span class="line">subject1.save()</span><br><span class="line">subject2 = Subject(name=<span class="string">&#x27;全栈软件测试&#x27;</span>, intro=<span class="string">&#x27;学习自动化测试的学科&#x27;</span>, is_hot=<span class="literal">False</span>)</span><br><span class="line">subject2.save()</span><br><span class="line">subject3 = Subject(name=<span class="string">&#x27;JavaEE分布式开发&#x27;</span>, intro=<span class="string">&#x27;基于Java语言的服务器应用开发&#x27;</span>, is_hot=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure>

<h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">subject = Subject.objects.get(no=<span class="number">2</span>)</span><br><span class="line">subject.delete()</span><br></pre></td></tr></table></figure>

<h4 id="更新"><a href="#更新" class="headerlink" title="更新"></a>更新</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">subject = Subject.objects.get(no=1)</span><br><span class="line">subject.name = &#x27;Python全栈+人工智能&#x27;</span><br><span class="line">subject.save()</span><br></pre></td></tr></table></figure>

<h4 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h4><ol>
<li>查询所有对象。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Subjects.objects.all()</span><br></pre></td></tr></table></figure>

<ol start="2">
<li>过滤数据。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查询名称为“Python全栈+人工智能”的学科</span></span><br><span class="line">Subject.objects.filter(name=&#x27;Python全栈+人工智能&#x27;)</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询名称包含“全栈”的学科（模糊查询）</span></span><br><span class="line">Subject.objects.filter(name__contains=&#x27;全栈&#x27;)</span><br><span class="line">Subject.objects.filter(name__startswith=&#x27;全栈&#x27;)</span><br><span class="line">Subject.objects.filter(name__endswith=&#x27;全栈&#x27;)</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询所有热门学科</span></span><br><span class="line">Subject.objects.filter(is_hot=True)</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询编号大于3小于10的学科</span></span><br><span class="line">Subject.objects.filter(no__gt=3).filter(no__lt=10)</span><br><span class="line">Subject.objects.filter(no__gt=3, no__lt=10)</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询编号在3到7之间的学科</span></span><br><span class="line">Subject.objects.filter(no__ge=3, no__le=7)</span><br><span class="line">Subject.objects.filter(no__range=(3, 7))</span><br></pre></td></tr></table></figure>

<ol start="3">
<li>查询单个对象。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查询主键为1的学科</span></span><br><span class="line">Subject.objects.get(pk=1)</span><br><span class="line">Subject.objects.get(no=1)</span><br><span class="line">Subject.objects.filter(no=1).first()</span><br><span class="line">Subject.objects.filter(no=1).last()</span><br></pre></td></tr></table></figure>

<ol start="4">
<li>排序。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查询所有学科按编号升序排列</span></span><br><span class="line">Subject.objects.order_by(&#x27;no&#x27;)</span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询所有部门按部门编号降序排列</span></span><br><span class="line">Subject.objects.order_by(&#x27;-no&#x27;)</span><br></pre></td></tr></table></figure>

<ol start="5">
<li>切片（分页查询）。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 按编号从小到大查询前3个学科</span></span><br><span class="line">Subject.objects.order_by(&#x27;no&#x27;)[:3]</span><br></pre></td></tr></table></figure>

<ol start="6">
<li>计数。</li>
</ol>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查询一共有多少个学科</span></span><br><span class="line">Subject.objects.count()</span><br></pre></td></tr></table></figure>

<ol start="7">
<li>高级查询。</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> 查询编号为1的学科的老师</span></span><br><span class="line">Teacher.objects.filter(subject__no=1)</span><br><span class="line">Subject.objects.get(pk=1).teacher_set.all() </span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 查询学科名称有“全栈”二字的学科的老师</span></span><br><span class="line">Teacher.objects.filter(subject__name__contains=&#x27;全栈&#x27;) </span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明1</strong>：由于老师与学科之间存在多对一外键关联，所以能通过学科反向查询到该学科的老师（从一对多关系中“一”的一方查询“多”的一方），反向查询属性默认的名字是<code>类名小写_set</code>（如上面例子中的<code>teacher_set</code>），当然也可以在创建模型时通过<code>ForeingKey</code>的<code>related_name</code>属性指定反向查询属性的名字。如果不希望执行反向查询可以将<code>related_name</code>属性设置为<code>&#39;+&#39;</code>或者以<code>&#39;+&#39;</code>开头的字符串。</p>
</blockquote>
<blockquote>
<p><strong>说明2</strong>：ORM查询多个对象时会返回QuerySet对象，QuerySet使用了惰性查询，即在创建QuerySet对象的过程中不涉及任何数据库活动，等真正用到对象时（对QuerySet求值）才向数据库发送SQL语句并获取对应的结果，这一点在实际开发中需要引起注意！</p>
</blockquote>
<blockquote>
<p><strong>说明3</strong>：如果希望更新多条数据，不用先逐一获取模型对象再修改对象属性，可以直接使用QuerySet对象的<code>update()</code>方法一次性更新多条数据。</p>
</blockquote>
<h3 id="利用Django后台管理模型"><a href="#利用Django后台管理模型" class="headerlink" title="利用Django后台管理模型"></a>利用Django后台管理模型</h3><p>在创建好模型类之后，可以通过Django框架自带的后台管理应用（<code>admin</code>应用）实现对模型的管理。虽然实际应用中，这个后台可能并不能满足我们的需求，但是在学习Django框架时，我们可以利用<code>admin</code>应用来管理我们的模型，同时也通过它来了解一个项目的后台管理系统需要哪些功能。使用Django自带的<code>admin</code>应用步骤如下所示。</p>
<ol>
<li><p>将<code>admin</code>应用所需的表迁移到数据库中。<code>admin</code>应用本身也需要数据库的支持，而且在<code>admin</code>应用中已经定义好了相关的数据模型类，我们只需要通过模型迁移操作就能自动在数据库中生成所需的二维表。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py migrate</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建访问<code>admin</code>应用的超级用户账号，这里需要输入用户名、邮箱和口令。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py createsuperuser</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：输入口令时没有回显也不能退格，需要一气呵成完成输入。</p>
</blockquote>
</li>
<li><p>运行项目，在浏览器中访问<code>http://127.0.0.1:8000/admin</code>，输入刚才创建的超级用户账号和密码进行登录。</p>
<p><img src="/Users/Hao/Desktop/Python-100-Days/Day41-55/res/django-admin-login.png" alt="img"></p>
<p>登录后进入管理员操作平台。</p>
<p><img src="http://iubest.gitee.io/pic/django-admin-apps.png" alt="img"></p>
<p>注意，我们暂时还没能在<code>admin</code>应用中看到之前创建的模型类，为此需要在<code>polls</code>应用的<code>admin.py</code>文件中对需要管理的模型进行注册。</p>
</li>
<li><p>注册模型类。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> polls.models <span class="keyword">import</span> Subject, Teacher</span><br><span class="line"></span><br><span class="line">admin.site.register(Subject)</span><br><span class="line">admin.site.register(Teacher)</span><br></pre></td></tr></table></figure>

<p>注册模型类后，就可以在后台管理系统中看到它们。</p>
<p><img src="http://iubest.gitee.io/pic/django-admin-models.png" alt="img"></p>
</li>
<li><p>对模型进行CRUD操作。</p>
<p>可以在管理员平台对模型进行C（新增）、R（查看）、U（更新）、D（删除）操作，如下图所示。</p>
<ul>
<li><p>添加学科。</p>
<p>  <img src="http://iubest.gitee.io/pic/django-admin-add-model.png" alt="img"></p>
</li>
<li><p>查看所有学科。</p>
<p>  <img src="http://iubest.gitee.io/pic/django-admin-view-models.png" alt="img"></p>
</li>
<li><p>删除和更新学科。</p>
<p>  <img src="http://iubest.gitee.io/pic/django-admin-delete-update-model.png" alt="img"></p>
</li>
</ul>
</li>
<li><p>注册模型管理类。</p>
<p>可能大家已经注意到了，刚才在后台查看部门信息的时候，显示的部门信息并不直观，为此我们再修改<code>admin.py</code>文件，通过注册模型管理类，可以在后台管理系统中更好的管理模型。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> polls.models <span class="keyword">import</span> Subject, Teacher</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubjectModelAdmin</span>(<span class="params">admin.ModelAdmin</span>):</span></span><br><span class="line">    list_display = (<span class="string">&#x27;no&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;intro&#x27;</span>, <span class="string">&#x27;is_hot&#x27;</span>)</span><br><span class="line">    search_fields = (<span class="string">&#x27;name&#x27;</span>, )</span><br><span class="line">    ordering = (<span class="string">&#x27;no&#x27;</span>, )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TeacherModelAdmin</span>(<span class="params">admin.ModelAdmin</span>):</span></span><br><span class="line">    list_display = (<span class="string">&#x27;no&#x27;</span>, <span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;sex&#x27;</span>, <span class="string">&#x27;birth&#x27;</span>, <span class="string">&#x27;good_count&#x27;</span>, <span class="string">&#x27;bad_count&#x27;</span>, <span class="string">&#x27;subject&#x27;</span>)</span><br><span class="line">    search_fields = (<span class="string">&#x27;name&#x27;</span>, )</span><br><span class="line">    ordering = (<span class="string">&#x27;no&#x27;</span>, )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">admin.site.register(Subject, SubjectModelAdmin)</span><br><span class="line">admin.site.register(Teacher, TeacherModelAdmin)</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/django-admin-view-models-subject.png" alt="img"></p>
<p><img src="http://iubest.gitee.io/pic/django-admin-view-models-teacher.png" alt="img"></p>
<p>为了更好的查看模型，我们为<code>Subject</code>类添加<code>__str__</code>魔法方法，并在该方法中返回学科名字。这样在如上图所示的查看老师的页面上显示老师所属学科时，就不再是<code>Subject object(1)</code>这样晦涩的信息，而是学科的名称。</p>
</li>
</ol>
<h3 id="实现学科页和老师页效果"><a href="#实现学科页和老师页效果" class="headerlink" title="实现学科页和老师页效果"></a>实现学科页和老师页效果</h3><ol>
<li><p>修改<code>polls/views.py</code>文件，编写视图函数实现对学科页和老师页的渲染。</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.shortcuts <span class="keyword">import</span> render, redirect</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> polls.models <span class="keyword">import</span> Subject, Teacher</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_subjects</span>(<span class="params">request</span>):</span></span><br><span class="line">    subjects = Subject.objects.all().order_by(<span class="string">&#x27;no&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> render(request, <span class="string">&#x27;subjects.html&#x27;</span>, &#123;<span class="string">&#x27;subjects&#x27;</span>: subjects&#125;)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_teachers</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        sno = int(request.GET.get(<span class="string">&#x27;sno&#x27;</span>))</span><br><span class="line">        teachers = []</span><br><span class="line">        <span class="keyword">if</span> sno:</span><br><span class="line">            subject = Subject.objects.only(<span class="string">&#x27;name&#x27;</span>).get(no=sno)</span><br><span class="line">            teachers = Teacher.objects.filter(subject=subject).order_by(<span class="string">&#x27;no&#x27;</span>)</span><br><span class="line">        <span class="keyword">return</span> render(request, <span class="string">&#x27;teachers.html&#x27;</span>, &#123;</span><br><span class="line">            <span class="string">&#x27;subject&#x27;</span>: subject,</span><br><span class="line">            <span class="string">&#x27;teachers&#x27;</span>: teachers</span><br><span class="line">        &#125;)</span><br><span class="line">    <span class="keyword">except</span> (ValueError, Subject.DoesNotExist):</span><br><span class="line">        <span class="keyword">return</span> redirect(<span class="string">&#x27;/&#x27;</span>)</span><br></pre></td></tr></table></figure>
</li>
<li><p>修改<code>templates/subjects.html</code>和<code>templates/teachers.html</code>模板页。</p>
<p> <code>subjects.html</code></p>
  <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>学科信息<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">        <span class="selector-id">#container</span> &#123;</span></span><br><span class="line">            width: 80%;</span><br><span class="line">            margin: 10px auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.user</span> &#123;</span></span><br><span class="line">            float: right;</span><br><span class="line">            margin-right: 10px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.user</span>&gt;<span class="selector-tag">a</span> &#123;</span></span><br><span class="line">            margin-right: 10px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-id">#main</span>&gt;<span class="selector-tag">dl</span>&gt;<span class="selector-tag">dt</span> &#123;</span></span><br><span class="line"><span class="css">            <span class="selector-tag">font-size</span>: 1<span class="selector-class">.5em</span>;</span></span><br><span class="line">            font-weight: bold;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-id">#main</span>&gt;<span class="selector-tag">dl</span>&gt;<span class="selector-tag">dd</span> &#123;</span></span><br><span class="line"><span class="css">            <span class="selector-tag">font-size</span>: 1<span class="selector-class">.2em</span>;</span></span><br><span class="line">        &#125;</span><br><span class="line">        a &#123;</span><br><span class="line">            text-decoration: none;</span><br><span class="line">            color: darkcyan;</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;user&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;login.html&quot;</span>&gt;</span>用户登录<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;register.html&quot;</span>&gt;</span>快速注册<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>扣丁学堂所有学科<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;main&quot;</span>&gt;</span></span><br><span class="line">            &#123;% for subject in subjects %&#125;</span><br><span class="line">            <span class="tag">&lt;<span class="name">dl</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dt</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/teachers/?sno=&#123;&#123; subject.no &#125;&#125;&quot;</span>&gt;</span>&#123;&#123; subject.name &#125;&#125;<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">                    &#123;% if subject.is_hot %&#125;</span><br><span class="line">                    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;/static/images/hot-icon-small.png&quot;</span>&gt;</span></span><br><span class="line">                    &#123;% endif %&#125;</span><br><span class="line">                <span class="tag">&lt;/<span class="name">dt</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">dd</span>&gt;</span>&#123;&#123; subject.intro &#125;&#125;<span class="tag">&lt;/<span class="name">dd</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dl</span>&gt;</span></span><br><span class="line">            &#123;% endfor %&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p> <code>teachers.html</code></p>
 <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>老师信息<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">        <span class="selector-id">#container</span> &#123;</span></span><br><span class="line">            width: 80%;</span><br><span class="line">            margin: 10px auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.teacher</span> &#123;</span></span><br><span class="line">            width: 100%;</span><br><span class="line">            margin: 0 auto;</span><br><span class="line">            padding: 10px 0;</span><br><span class="line">            border-bottom: 1px dashed gray;</span><br><span class="line">            overflow: auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.teacher</span>&gt;<span class="selector-tag">div</span> &#123;</span></span><br><span class="line">            float: left;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.photo</span> &#123;</span></span><br><span class="line">            height: 140px;</span><br><span class="line">            border-radius: 75px;</span><br><span class="line">            overflow: hidden;</span><br><span class="line">            margin-left: 20px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> &#123;</span></span><br><span class="line">            width: 75%;</span><br><span class="line">            margin-left: 30px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">div</span> &#123;</span></span><br><span class="line">            clear: both;</span><br><span class="line">            margin: 5px 10px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">span</span> &#123;</span></span><br><span class="line">            margin-right: 25px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.info</span> <span class="selector-tag">a</span> &#123;</span></span><br><span class="line">            text-decoration: none;</span><br><span class="line">            color: darkcyan;</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#123; subject.name &#125;&#125;学科的老师信息<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        &#123;% if not teachers %&#125;</span><br><span class="line">            <span class="tag">&lt;<span class="name">h2</span>&gt;</span>暂无该学科老师信息<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">        &#123;% endif %&#125;</span><br><span class="line">        &#123;% for teacher in teachers %&#125;</span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;teacher&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;photo&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;/static/images/&#123;&#123; teacher.photo &#125;&#125;&quot;</span> <span class="attr">height</span>=<span class="string">&quot;140&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;info&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">strong</span>&gt;</span>姓名：&#123;&#123; teacher.name &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>性别：&#123;&#123; teacher.sex | yesno:&#x27;男,女&#x27; &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>出生日期：&#123;&#123; teacher.birth | date:&#x27;Y年n月j日&#x27;&#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;intro&quot;</span>&gt;</span>&#123;&#123; teacher.intro &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;comment&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&quot;</span>&gt;</span>好评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span>(<span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.good_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                    <span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&quot;</span>&gt;</span>差评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.bad_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        &#123;% endfor %&#125;</span><br><span class="line">        <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>修改<code>vote/urls.py</code>文件，实现映射URL。</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> polls.views <span class="keyword">import</span> show_subjects, show_teachers</span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line">    path(<span class="string">&#x27;admin/&#x27;</span>, admin.site.urls),</span><br><span class="line">    path(<span class="string">&#x27;&#x27;</span>, show_subjects),</span><br><span class="line">    path(<span class="string">&#x27;teachers/&#x27;</span>, show_teachers),</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

</li>
</ol>
<p>到此为止，页面上需要的图片（静态资源）还没有能够正常展示，我们在下一章节中为大家介绍如何处理模板页上的需要的静态资源。</p>
<h3 id="补充内容"><a href="#补充内容" class="headerlink" title="补充内容"></a>补充内容</h3><h4 id="Django模型最佳实践"><a href="#Django模型最佳实践" class="headerlink" title="Django模型最佳实践"></a>Django模型最佳实践</h4><ol>
<li>正确的为模型和关系字段命名。</li>
<li>设置适当的<code>related_name</code>属性。</li>
<li>用<code>OneToOneField</code>代替<code>ForeignKeyField(unique=True)</code>。</li>
<li>通过“迁移操作”（migrate）来添加模型。</li>
<li>用NoSQL来应对需要降低范式级别的场景。</li>
<li>如果布尔类型可以为空要使用<code>NullBooleanField</code>。</li>
<li>在模型中放置业务逻辑。</li>
<li>用<code>&lt;ModelName&gt;.DoesNotExists</code>取代<code>ObjectDoesNotExists</code>。</li>
<li>在数据库中不要出现无效数据。</li>
<li>不要对<code>QuerySet</code>调用<code>len()</code>函数。</li>
<li>将<code>QuerySet</code>的<code>exists()</code>方法的返回值用于<code>if</code>条件。</li>
<li>用<code>DecimalField</code>来存储货币相关数据而不是<code>FloatField</code>。</li>
<li>定义<code>__str__</code>方法。</li>
<li>不要将数据文件放在同一个目录中。</li>
</ol>
<blockquote>
<p><strong>说明</strong>：以上内容来自于STEELKIWI网站的<a target="_blank" rel="noopener" href="https://steelkiwi.com/blog/best-practices-working-django-models-python/"><em>Best Practice working with Django models in Python</em></a>，有兴趣的小伙伴可以阅读原文。</p>
</blockquote>
<h4 id="模型定义参考"><a href="#模型定义参考" class="headerlink" title="模型定义参考"></a>模型定义参考</h4><h5 id="字段"><a href="#字段" class="headerlink" title="字段"></a>字段</h5><p>对字段名称的限制</p>
<ul>
<li>字段名不能是Python的保留字，否则会导致语法错误</li>
<li>字段名不能有多个连续下划线，否则影响ORM查询操作</li>
</ul>
<p>Django模型字段类</p>
<table>
<thead>
<tr>
<th>字段类</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td><code>AutoField</code></td>
<td>自增ID字段</td>
</tr>
<tr>
<td><code>BigIntegerField</code></td>
<td>64位有符号整数</td>
</tr>
<tr>
<td><code>BinaryField</code></td>
<td>存储二进制数据的字段，对应Python的<code>bytes</code>类型</td>
</tr>
<tr>
<td><code>BooleanField</code></td>
<td>存储<code>True</code>或<code>False</code></td>
</tr>
<tr>
<td><code>CharField</code></td>
<td>长度较小的字符串</td>
</tr>
<tr>
<td><code>DateField</code></td>
<td>存储日期，有<code>auto_now</code>和<code>auto_now_add</code>属性</td>
</tr>
<tr>
<td><code>DateTimeField</code></td>
<td>存储日期和日期，两个附加属性同上</td>
</tr>
<tr>
<td><code>DecimalField</code></td>
<td>存储固定精度小数，有<code>max_digits</code>（有效位数）和<code>decimal_places</code>（小数点后面）两个必要的参数</td>
</tr>
<tr>
<td><code>DurationField</code></td>
<td>存储时间跨度</td>
</tr>
<tr>
<td><code>EmailField</code></td>
<td>与<code>CharField</code>相同，可以用<code>EmailValidator</code>验证</td>
</tr>
<tr>
<td><code>FileField</code></td>
<td>文件上传字段</td>
</tr>
<tr>
<td><code>FloatField</code></td>
<td>存储浮点数</td>
</tr>
<tr>
<td><code>ImageField</code></td>
<td>其他同<code>FileFiled</code>，要验证上传的是不是有效图像</td>
</tr>
<tr>
<td><code>IntegerField</code></td>
<td>存储32位有符号整数。</td>
</tr>
<tr>
<td><code>GenericIPAddressField</code></td>
<td>存储IPv4或IPv6地址</td>
</tr>
<tr>
<td><code>NullBooleanField</code></td>
<td>存储<code>True</code>、<code>False</code>或<code>null</code>值</td>
</tr>
<tr>
<td><code>PositiveIntegerField</code></td>
<td>存储无符号整数（只能存储正数）</td>
</tr>
<tr>
<td><code>SlugField</code></td>
<td>存储slug（简短标注）</td>
</tr>
<tr>
<td><code>SmallIntegerField</code></td>
<td>存储16位有符号整数</td>
</tr>
<tr>
<td><code>TextField</code></td>
<td>存储数据量较大的文本</td>
</tr>
<tr>
<td><code>TimeField</code></td>
<td>存储时间</td>
</tr>
<tr>
<td><code>URLField</code></td>
<td>存储URL的<code>CharField</code></td>
</tr>
<tr>
<td><code>UUIDField</code></td>
<td>存储全局唯一标识符</td>
</tr>
</tbody></table>
<h5 id="字段属性"><a href="#字段属性" class="headerlink" title="字段属性"></a>字段属性</h5><p>通用字段属性</p>
<table>
<thead>
<tr>
<th>选项</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td><code>null</code></td>
<td>数据库中对应的字段是否允许为<code>NULL</code>，默认为<code>False</code></td>
</tr>
<tr>
<td><code>blank</code></td>
<td>后台模型管理验证数据时，是否允许为<code>NULL</code>，默认为<code>False</code></td>
</tr>
<tr>
<td><code>choices</code></td>
<td>设定字段的选项，各元组中的第一个值是设置在模型上的值，第二值是人类可读的值</td>
</tr>
<tr>
<td><code>db_column</code></td>
<td>字段对应到数据库表中的列名，未指定时直接使用字段的名称</td>
</tr>
<tr>
<td><code>db_index</code></td>
<td>设置为<code>True</code>时将在该字段创建索引</td>
</tr>
<tr>
<td><code>db_tablespace</code></td>
<td>为有索引的字段设置使用的表空间，默认为<code>DEFAULT_INDEX_TABLESPACE</code></td>
</tr>
<tr>
<td><code>default</code></td>
<td>字段的默认值</td>
</tr>
<tr>
<td><code>editable</code></td>
<td>字段在后台模型管理或<code>ModelForm</code>中是否显示，默认为<code>True</code></td>
</tr>
<tr>
<td><code>error_messages</code></td>
<td>设定字段抛出异常时的默认消息的字典，其中的键包括<code>null</code>、<code>blank</code>、<code>invalid</code>、<code>invalid_choice</code>、<code>unique</code>和<code>unique_for_date</code></td>
</tr>
<tr>
<td><code>help_text</code></td>
<td>表单小组件旁边显示的额外的帮助文本。</td>
</tr>
<tr>
<td><code>primary_key</code></td>
<td>将字段指定为模型的主键，未指定时会自动添加<code>AutoField</code>用于主键，只读。</td>
</tr>
<tr>
<td><code>unique</code></td>
<td>设置为<code>True</code>时，表中字段的值必须是唯一的</td>
</tr>
<tr>
<td><code>verbose_name</code></td>
<td>字段在后台模型管理显示的名称，未指定时使用字段的名称</td>
</tr>
</tbody></table>
<p><code>ForeignKey</code>属性</p>
<ol>
<li><code>limit_choices_to</code>：值是一个Q对象或返回一个Q对象，用于限制后台显示哪些对象。</li>
<li><code>related_name</code>：用于获取关联对象的关联管理器对象（反向查询），如果不允许反向，该属性应该被设置为<code>&#39;+&#39;</code>，或者以<code>&#39;+&#39;</code>结尾。</li>
<li><code>to_field</code>：指定关联的字段，默认关联对象的主键字段。</li>
<li><code>db_constraint</code>：是否为外键创建约束，默认值为<code>True</code>。</li>
<li><code>on_delete</code>：外键关联的对象被删除时对应的动作，可取的值包括<code>django.db.models</code>中定义的：<ul>
<li><code>CASCADE</code>：级联删除。</li>
<li><code>PROTECT</code>：抛出<code>ProtectedError</code>异常，阻止删除引用的对象。</li>
<li><code>SET_NULL</code>：把外键设置为<code>null</code>，当<code>null</code>属性被设置为<code>True</code>时才能这么做。</li>
<li><code>SET_DEFAULT</code>：把外键设置为默认值，提供了默认值才能这么做。</li>
</ul>
</li>
</ol>
<p><code>ManyToManyField</code>属性</p>
<ol>
<li><code>symmetrical</code>：是否建立对称的多对多关系。</li>
<li><code>through</code>：指定维持多对多关系的中间表的Django模型。</li>
<li><code>throughfields</code>：定义了中间模型时可以指定建立多对多关系的字段。</li>
<li><code>db_table</code>：指定维持多对多关系的中间表的表名。</li>
</ol>
<h5 id="模型元数据选项"><a href="#模型元数据选项" class="headerlink" title="模型元数据选项"></a>模型元数据选项</h5><table>
<thead>
<tr>
<th>选项</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td><code>abstract</code></td>
<td>设置为True时模型是抽象父类</td>
</tr>
<tr>
<td><code>app_label</code></td>
<td>如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定</td>
</tr>
<tr>
<td><code>db_table</code></td>
<td>模型使用的数据表名称</td>
</tr>
<tr>
<td><code>db_tablespace</code></td>
<td>模型使用的数据表空间</td>
</tr>
<tr>
<td><code>default_related_name</code></td>
<td>关联对象回指这个模型时默认使用的名称，默认为<model_name>_set</td>
</tr>
<tr>
<td><code>get_latest_by</code></td>
<td>模型中可排序字段的名称。</td>
</tr>
<tr>
<td><code>managed</code></td>
<td>设置为True时，Django在迁移中创建数据表并在执行flush管理命令时把表移除</td>
</tr>
<tr>
<td><code>order_with_respect_to</code></td>
<td>标记对象为可排序的</td>
</tr>
<tr>
<td><code>ordering</code></td>
<td>对象的默认排序</td>
</tr>
<tr>
<td><code>permissions</code></td>
<td>创建对象时写入权限表的额外权限</td>
</tr>
<tr>
<td><code>default_permissions</code></td>
<td>默认为<code>(&#39;add&#39;, &#39;change&#39;, &#39;delete&#39;)</code></td>
</tr>
<tr>
<td><code>unique_together</code></td>
<td>设定组合在一起时必须独一无二的字段名</td>
</tr>
<tr>
<td><code>index_together</code></td>
<td>设定一起建立索引的多个字段名</td>
</tr>
<tr>
<td><code>verbose_name</code></td>
<td>为对象设定人类可读的名称</td>
</tr>
<tr>
<td><code>verbose_name_plural</code></td>
<td>设定对象的复数名称</td>
</tr>
</tbody></table>
<h4 id="查询参考"><a href="#查询参考" class="headerlink" title="查询参考"></a>查询参考</h4><h5 id="按字段查找可以用的条件"><a href="#按字段查找可以用的条件" class="headerlink" title="按字段查找可以用的条件"></a>按字段查找可以用的条件</h5><ol>
<li><code>exact</code> / <code>iexact</code>：精确匹配/忽略大小写的精确匹配查询</li>
<li><code>contains</code> / <code>icontains</code> / <code>startswith</code> / <code>istartswith</code> / <code>endswith</code> / <code>iendswith</code>：基于<code>like</code>的模糊查询</li>
<li><code>in</code> ：集合运算</li>
<li><code>gt</code> / <code>gte</code> / <code>lt</code> / <code>lte</code>：大于/大于等于/小于/小于等于关系运算</li>
<li><code>range</code>：指定范围查询（SQL中的<code>between…and…</code>）</li>
<li><code>year</code> / <code>month</code> / <code>day</code> / <code>week_day</code> / <code>hour</code> / <code>minute</code> / <code>second</code>：查询时间日期</li>
<li><code>isnull</code>：查询空值（True）或非空值（False）</li>
<li><code>search</code>：基于全文索引的全文检索（一般很少使用）</li>
<li><code>regex</code> / <code>iregex</code>：基于正则表达式的模糊匹配查询</li>
</ol>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/4-Cookie和Session"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/4-Cookie%E5%92%8CSession/"
    >4-Cookie和Session.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/4-Cookie%E5%92%8CSession/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="Cookie和Session"><a href="#Cookie和Session" class="headerlink" title="Cookie和Session"></a>Cookie和Session</h2><p>我们继续来完成上一章节中的项目，实现“用户登录”的功能，并限制只有登录的用户才能投票。</p>
<h3 id="用户登录的准备工作"><a href="#用户登录的准备工作" class="headerlink" title="用户登录的准备工作"></a>用户登录的准备工作</h3><p>我们先为实现用户登录做一些准备工作。</p>
<ol>
<li><p>创建用户模型。之前我们讲解过如果通过Django的ORM实现从二维表到模型的转换（反向工程），这次我们尝试把模型变成二维表（正向工程）。</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span>(<span class="params">models.Model</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;用户&quot;&quot;&quot;</span></span><br><span class="line">    no = models.AutoField(primary_key=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;编号&#x27;</span>)</span><br><span class="line">    username = models.CharField(max_length=<span class="number">20</span>, unique=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;用户名&#x27;</span>)</span><br><span class="line">    password = models.CharField(max_length=<span class="number">32</span>, verbose_name=<span class="string">&#x27;密码&#x27;</span>)</span><br><span class="line">    tel = models.CharField(max_length=<span class="number">20</span>, verbose_name=<span class="string">&#x27;手机号&#x27;</span>)</span><br><span class="line">    reg_date = models.DateTimeField(auto_now_add=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;注册时间&#x27;</span>)</span><br><span class="line">    last_visit = models.DateTimeField(null=<span class="literal">True</span>, verbose_name=<span class="string">&#x27;最后登录时间&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        db_table = <span class="string">&#x27;tb_user&#x27;</span></span><br><span class="line">        verbose_name = <span class="string">&#x27;用户&#x27;</span></span><br><span class="line">        verbose_name_plural = <span class="string">&#x27;用户&#x27;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>使用下面的命令生成迁移文件并执行迁移，将<code>User</code>模型直接变成关系型数据库中的二维表<code>tb_user</code>。</p>
 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python manage.py makemigrations polls</span><br><span class="line">python manage.py migrate polls</span><br></pre></td></tr></table></figure>
</li>
<li><p>用下面的SQL语句直接插入两条测试数据，通常不能讲用户的密码直接保存在数据库中，因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数（散列函数），可以产生出一个128位（比特）的哈希值（散列值），用于确保信息传输完整一致。在使用哈希值时，通常会将哈希值表示为16进制字符串，因此128位的MD5摘要通常表示为32个十六进制符号。</p>
 <figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert</span> <span class="keyword">into</span> <span class="string">`tb_user`</span></span><br><span class="line">    (<span class="string">`username`</span>, <span class="string">`password`</span>, <span class="string">`tel`</span>, <span class="string">`reg_date`</span>)</span><br><span class="line"><span class="keyword">values</span></span><br><span class="line">    (<span class="string">&#x27;wangdachui&#x27;</span>, <span class="string">&#x27;1c63129ae9db9c60c3e8aa94d3e00495&#x27;</span>, <span class="string">&#x27;13122334455&#x27;</span>, <span class="keyword">now</span>()),</span><br><span class="line">    (<span class="string">&#x27;hellokitty&#x27;</span>, <span class="string">&#x27;c6f8cf68e5f68b0aa4680e089ee4742c&#x27;</span>, <span class="string">&#x27;13890006789&#x27;</span>, <span class="keyword">now</span>());</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：上面创建的两个用户<code>wangdachui</code>和<code>hellokitty</code>密码分别是<code>1qaz2wsx</code>和<code>Abc123!!</code>。</p>
</blockquote>
</li>
<li><p>我们在应用下增加一个名为<code>utils.py</code>的模块用来保存需要使用的工具函数。Python标准库中的<code>hashlib</code>模块封装了常用的哈希算法，包括：MD5、SHA1、SHA256等。下面是使用<code>hashlib</code>中的<code>md5</code>类将字符串处理成MD5摘要的函数如下所示。</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gen_md5_digest</span>(<span class="params">content</span>):</span></span><br><span class="line">    <span class="keyword">return</span> hashlib.md5(content.encode()).hexdigest()</span><br></pre></td></tr></table></figure>
</li>
<li><p>编写用户登录的视图函数和模板页。</p>
<p> 添加渲染登录页面的视图函数：</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">login</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    hint = <span class="string">&#x27;&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> render(request, <span class="string">&#x27;login.html&#x27;</span>, &#123;<span class="string">&#x27;hint&#x27;</span>: hint&#125;)</span><br></pre></td></tr></table></figure>

<p> 增加<code>login.html</code>模板页：</p>
 <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>用户登录<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">        <span class="selector-id">#container</span> &#123;</span></span><br><span class="line">            width: 520px;</span><br><span class="line">            margin: 10px auto;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.input</span> &#123;</span></span><br><span class="line">            margin: 20px 0;</span><br><span class="line">            width: 460px;</span><br><span class="line">            height: 40px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.input</span>&gt;<span class="selector-tag">label</span> &#123;</span></span><br><span class="line">            display: inline-block;</span><br><span class="line">            width: 140px;</span><br><span class="line">            text-align: right;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.input</span>&gt;<span class="selector-tag">img</span> &#123;</span></span><br><span class="line">            width: 150px;</span><br><span class="line">            vertical-align: middle;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-tag">input</span><span class="selector-attr">[name=captcha]</span> &#123;</span></span><br><span class="line">            vertical-align: middle;</span><br><span class="line">        &#125;</span><br><span class="line">        form+div &#123;</span><br><span class="line">            margin-top: 20px;</span><br><span class="line">        &#125;</span><br><span class="line">        form+div&gt;a &#123;</span><br><span class="line">            text-decoration: none;</span><br><span class="line">            color: darkcyan;</span><br><span class="line"><span class="css">            <span class="selector-tag">font-size</span>: 1<span class="selector-class">.2em</span>;</span></span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.button</span> &#123;</span></span><br><span class="line">            width: 500px;</span><br><span class="line">            text-align: center;</span><br><span class="line">            margin-top: 20px;</span><br><span class="line">        &#125;</span><br><span class="line"><span class="css">        <span class="selector-class">.hint</span> &#123;</span></span><br><span class="line">            color: red;</span><br><span class="line">            font-size: 12px;</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>用户登录<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">class</span>=<span class="string">&quot;hint&quot;</span>&gt;</span>&#123;&#123; hint &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">&quot;/login/&quot;</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span>&gt;</span></span><br><span class="line">            &#123;% csrf_token %&#125;</span><br><span class="line">            <span class="tag">&lt;<span class="name">fieldset</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">legend</span>&gt;</span>用户信息<span class="tag">&lt;/<span class="name">legend</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;input&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">label</span>&gt;</span>用户名：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;username&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;input&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">label</span>&gt;</span>密码：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;password&quot;</span> <span class="attr">name</span>=<span class="string">&quot;password&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;input&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">label</span>&gt;</span>验证码：<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;captcha&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">img</span> <span class="attr">id</span>=<span class="string">&quot;code&quot;</span> <span class="attr">src</span>=<span class="string">&quot;/captcha/&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span> <span class="attr">width</span>=<span class="string">&quot;150&quot;</span> <span class="attr">height</span>=<span class="string">&quot;40&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">fieldset</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;button&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">value</span>=<span class="string">&quot;登录&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;reset&quot;</span> <span class="attr">value</span>=<span class="string">&quot;重置&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/register/&quot;</span>&gt;</span>注册新用户<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p> 注意，在上面的表单中，我们使用了模板指令<code>&#123;% csrf_token %&#125;</code>为表单添加一个隐藏域（大家可以在浏览器中显示网页源代码就可以看到这个指令生成的<code>type</code>属性为<code>hidden</code>的<code>input</code>标签），它的作用是在表单中生成一个随机令牌（token）来防范<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0">跨站请求伪造</a>（简称为CSRF），这也是Django在提交表单时的硬性要求。如果我们的表单中没有这样的令牌，那么提交表单时，Django框架会产生一个响应状态码为<code>403</code>的响应（禁止访问），除非我们设置了免除CSRF令牌。下图是一个关于CSRF简单生动的例子。</p>
<p> <img src="http://iubest.gitee.io/pic/csrf-simple.png" alt="img"></p>
</li>
</ol>
<p>接下来，我们可以编写提供验证码和实现用户登录的视图函数，在此之前，我们先说说一个Web应用实现用户跟踪的方式以及Django框架对实现用户跟踪所提供的支持。对一个Web应用来说，用户登录成功后必然要让服务器能够记住该用户已经登录，这样服务器才能为这个用户提供更好的服务，而且上面说到的CSRF也是通过钓鱼网站来套取用户登录信息进行恶意操作的攻击手段，这些都是以用户跟踪技术为基础的。在理解了这些背景知识后，我们就清楚用户登录时到底需要执行哪些操作。</p>
<h3 id="实现用户跟踪"><a href="#实现用户跟踪" class="headerlink" title="实现用户跟踪"></a>实现用户跟踪</h3><p>如今，一个网站如果不通过某种方式记住你是谁以及你之前在网站的活动情况，失去的就是网站的可用性和便利性，继而很有可能导致网站用户的流式，所以记住一个用户（更专业的说法叫<strong>用户跟踪</strong>）对绝大多数Web应用来说都是必需的功能。</p>
<p>在服务器端，我们想记住一个用户最简单的办法就是创建一个对象，通过这个对象就可以把用户相关的信息都保存起来，这个对象就是我们常说的session（用户会话对象）。那么问题来了，HTTP本身是一个<strong>无连接</strong>（每次请求和响应的过程中，服务器一旦完成对客户端请求的响应之后就断开连接）、<strong>无状态</strong>（客户端再次发起对服务器的请求时，服务器无法得知这个客户端之前的任何信息）的协议，即便服务器通过session对象保留了用户数据，还得通过某种方式来确定当前的请求与之前保存过的哪一个session是有关联的。相信很多人都能想到，我们可以给每个session对象分配一个全局唯一的标识符来识别session对象，我们姑且称之为sessionid，每次客户端发起请求时，只要携带上这个sessionid，就有办法找到与之对应的session对象，从而实现在两次请求之间记住该用户的信息，也就是我们之前说的用户跟踪。</p>
<p>要让客户端记住并在每次请求时带上sessionid又有以下几种做法：</p>
<ol>
<li><p>URL重写。所谓URL重写就是在URL中携带sessionid，例如：<code>http://www.example.com/index.html?sessionid=123456</code>，服务器通过获取sessionid参数的值来取到与之对应的session对象。</p>
</li>
<li><p>隐藏域（隐式表单域）。在提交表单的时候，可以通过在表单中设置隐藏域向服务器发送额外的数据。例如：<code>&lt;input type=&quot;hidden&quot; name=&quot;sessionid&quot; value=&quot;123456&quot;&gt;</code>。</p>
</li>
<li><p>本地存储。现在的浏览器都支持多种本地存储方案，包括：cookie、localStorage、sessionStorage、IndexedDB等。在这些方案中，cookie是历史最为悠久也是被诟病得最多的一种方案，也是我们接下来首先为大家讲解的一种方案。简单的说，cookie是一种以键值对方式保存在浏览器临时文件中的数据，每次请求时，请求头中会携带本站点的cookie到服务器，那么只要将sessionid写入cookie，下次请求时服务器只要读取请求头中的cookie就能够获得这个sessionid，如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/sessionid_from_cookie.png" alt="img"></p>
<p>在HTML5时代要，除了cookie，还可以使用新的本地存储API来保存数据，就是刚才提到的localStorage、sessionStorage、IndexedDB等技术，如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/cookie_xstorage_indexeddb.png" alt="img"></p>
</li>
</ol>
<p><strong>总结一下</strong>，要实现用户跟踪，服务器端可以为每个用户会话创建一个session对象并将session对象的ID写入到浏览器的cookie中；用户下次请求服务器时，浏览器会在HTTP请求头中携带该网站保存的cookie信息，这样服务器就可以从cookie中找到session对象的ID并根据此ID获取到之前创建的session对象；由于session对象可以用键值对的方式保存用户数据，这样之前保存在session对象中的信息可以悉数取出，服务器也可以根据这些信息判定用户身份和了解用户偏好，为用户提供更好的个性化服务。</p>
<h3 id="Django框架对session的支持"><a href="#Django框架对session的支持" class="headerlink" title="Django框架对session的支持"></a>Django框架对session的支持</h3><p>在创建Django项目时，默认的配置文件<code>settings.py</code>文件中已经激活了一个名为<code>SessionMiddleware</code>的中间件（关于中间件的知识我们在后面的章节做详细讲解，这里只需要知道它的存在即可），因为这个中间件的存在，我们可以直接通过请求对象的<code>session</code>属性来操作会话对象。前面我们说过，<code>session</code>属性是一个像字典一样可以读写数据的容器对象，因此我们可以使用“键值对”的方式来保留用户数据。与此同时，<code>SessionMiddleware</code>中间件还封装了对cookie的操作，在cookie中保存了sessionid，这一点我们在上面已经提到过了。</p>
<p>在默认情况下，Django将session的数据序列化后保存在关系型数据库中，在Django 1.6以后的版本中，默认的序列化数据的方式是JSON序列化，而在此之前一直使用Pickle序列化。JSON序列化和Pickle序列化的差别在于前者将对象序列化为字符串（字符形式），而后者将对象序列化为字节串（二进制形式），因为安全方面的原因，JSON序列化成为了目前Django框架默认序列化数据的方式，这就要求在我们保存在session中的数据必须是能够JSON序列化的，否则就会引发异常。还有一点需要说明的是，使用关系型数据库保存session中的数据在大多数时候并不是最好的选择，因为数据库可能会承受巨大的压力而成为系统性能的瓶颈，在后面的章节中我们会告诉大家如何将session保存到缓存服务中以提升系统的性能。</p>
<h3 id="实现用户登录验证"><a href="#实现用户登录验证" class="headerlink" title="实现用户登录验证"></a>实现用户登录验证</h3><p>首先，我们在刚才的<code>polls/utils.py</code>文件中编写生成随机验证码的函数<code>gen_random_code</code>，内容如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line">ALL_CHARS = <span class="string">&#x27;0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gen_random_code</span>(<span class="params">length=<span class="number">4</span></span>):</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;&#x27;</span>.join(random.choices(ALL_CHARS, k=length))</span><br></pre></td></tr></table></figure>

<p>编写生成验证码图片的类<code>Captcha</code>。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">图片验证码</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> io <span class="keyword">import</span> BytesIO</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> Image</span><br><span class="line"><span class="keyword">from</span> PIL <span class="keyword">import</span> ImageFilter</span><br><span class="line"><span class="keyword">from</span> PIL.ImageDraw <span class="keyword">import</span> Draw</span><br><span class="line"><span class="keyword">from</span> PIL.ImageFont <span class="keyword">import</span> truetype</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Bezier</span>:</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;贝塞尔曲线&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self</span>):</span></span><br><span class="line">        self.tsequence = tuple([t / <span class="number">20.0</span> <span class="keyword">for</span> t <span class="keyword">in</span> range(<span class="number">21</span>)])</span><br><span class="line">        self.beziers = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">make_bezier</span>(<span class="params">self, n</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;绘制贝塞尔曲线&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            <span class="keyword">return</span> self.beziers[n]</span><br><span class="line">        <span class="keyword">except</span> KeyError:</span><br><span class="line">            combinations = pascal_row(n - <span class="number">1</span>)</span><br><span class="line">            result = []</span><br><span class="line">            <span class="keyword">for</span> t <span class="keyword">in</span> self.tsequence:</span><br><span class="line">                tpowers = (t ** i <span class="keyword">for</span> i <span class="keyword">in</span> range(n))</span><br><span class="line">                upowers = ((<span class="number">1</span> - t) ** i <span class="keyword">for</span> i <span class="keyword">in</span> range(n - <span class="number">1</span>, <span class="number">-1</span>, <span class="number">-1</span>))</span><br><span class="line">                coefs = [c * a * b <span class="keyword">for</span> c, a, b <span class="keyword">in</span> zip(combinations,</span><br><span class="line">                                                      tpowers, upowers)]</span><br><span class="line">                result.append(coefs)</span><br><span class="line">            self.beziers[n] = result</span><br><span class="line">            <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Captcha</span>:</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;验证码&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span>(<span class="params">self, width, height, fonts=None, color=None</span>):</span></span><br><span class="line">        self._image = <span class="literal">None</span></span><br><span class="line">        self._fonts = fonts <span class="keyword">if</span> fonts <span class="keyword">else</span> \</span><br><span class="line">            [os.path.join(os.path.dirname(__file__), <span class="string">&#x27;fonts&#x27;</span>, font)</span><br><span class="line">             <span class="keyword">for</span> font <span class="keyword">in</span> [<span class="string">&#x27;Arial.ttf&#x27;</span>, <span class="string">&#x27;Georgia.ttf&#x27;</span>, <span class="string">&#x27;Action.ttf&#x27;</span>]]</span><br><span class="line">        self._color = color <span class="keyword">if</span> color <span class="keyword">else</span> random_color(<span class="number">0</span>, <span class="number">200</span>, random.randint(<span class="number">220</span>, <span class="number">255</span>))</span><br><span class="line">        self._width, self._height = width, height</span><br><span class="line"></span><br><span class="line"><span class="meta">    @classmethod</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">instance</span>(<span class="params">cls, width=<span class="number">200</span>, height=<span class="number">75</span></span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;用于获取Captcha对象的类方法&quot;&quot;&quot;</span></span><br><span class="line">        prop_name = <span class="string">f&#x27;_instance_<span class="subst">&#123;width&#125;</span>_<span class="subst">&#123;height&#125;</span>&#x27;</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> hasattr(cls, prop_name):</span><br><span class="line">            setattr(cls, prop_name, cls(width, height))</span><br><span class="line">        <span class="keyword">return</span> getattr(cls, prop_name)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_background</span>(<span class="params">self</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;绘制背景&quot;&quot;&quot;</span></span><br><span class="line">        Draw(self._image).rectangle([(<span class="number">0</span>, <span class="number">0</span>), self._image.size],</span><br><span class="line">                                    fill=random_color(<span class="number">230</span>, <span class="number">255</span>))</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_smooth</span>(<span class="params">self</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;平滑图像&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> self._image.filter(ImageFilter.SMOOTH)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_curve</span>(<span class="params">self, width=<span class="number">4</span>, number=<span class="number">6</span>, color=None</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;绘制曲线&quot;&quot;&quot;</span></span><br><span class="line">        dx, height = self._image.size</span><br><span class="line">        dx /= number</span><br><span class="line">        path = [(dx * i, random.randint(<span class="number">0</span>, height))</span><br><span class="line">                <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>, number)]</span><br><span class="line">        bcoefs = Bezier().make_bezier(number - <span class="number">1</span>)</span><br><span class="line">        points = []</span><br><span class="line">        <span class="keyword">for</span> coefs <span class="keyword">in</span> bcoefs:</span><br><span class="line">            points.append(tuple(sum([coef * p <span class="keyword">for</span> coef, p <span class="keyword">in</span> zip(coefs, ps)])</span><br><span class="line">                                <span class="keyword">for</span> ps <span class="keyword">in</span> zip(*path)))</span><br><span class="line">        Draw(self._image).line(points, fill=color <span class="keyword">if</span> color <span class="keyword">else</span> self._color, width=width)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_noise</span>(<span class="params">self, number=<span class="number">50</span>, level=<span class="number">2</span>, color=None</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;绘制扰码&quot;&quot;&quot;</span></span><br><span class="line">        width, height = self._image.size</span><br><span class="line">        dx, dy = width / <span class="number">10</span>, height / <span class="number">10</span></span><br><span class="line">        width, height = width - dx, height - dy</span><br><span class="line">        draw = Draw(self._image)</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> range(number):</span><br><span class="line">            x = int(random.uniform(dx, width))</span><br><span class="line">            y = int(random.uniform(dy, height))</span><br><span class="line">            draw.line(((x, y), (x + level, y)),</span><br><span class="line">                      fill=color <span class="keyword">if</span> color <span class="keyword">else</span> self._color, width=level)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_text</span>(<span class="params">self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=<span class="number">0.75</span>, color=None</span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;绘制文本&quot;&quot;&quot;</span></span><br><span class="line">        color = color <span class="keyword">if</span> color <span class="keyword">else</span> self._color</span><br><span class="line">        fonts = tuple([truetype(name, size)</span><br><span class="line">                       <span class="keyword">for</span> name <span class="keyword">in</span> fonts</span><br><span class="line">                       <span class="keyword">for</span> size <span class="keyword">in</span> font_sizes <span class="keyword">or</span> (<span class="number">65</span>, <span class="number">70</span>, <span class="number">75</span>)])</span><br><span class="line">        draw = Draw(self._image)</span><br><span class="line">        char_images = []</span><br><span class="line">        <span class="keyword">for</span> c <span class="keyword">in</span> captcha_text:</span><br><span class="line">            font = random.choice(fonts)</span><br><span class="line">            c_width, c_height = draw.textsize(c, font=font)</span><br><span class="line">            char_image = Image.new(<span class="string">&#x27;RGB&#x27;</span>, (c_width, c_height), (<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>))</span><br><span class="line">            char_draw = Draw(char_image)</span><br><span class="line">            char_draw.text((<span class="number">0</span>, <span class="number">0</span>), c, font=font, fill=color)</span><br><span class="line">            char_image = char_image.crop(char_image.getbbox())</span><br><span class="line">            <span class="keyword">for</span> drawing <span class="keyword">in</span> drawings:</span><br><span class="line">                d = getattr(self, drawing)</span><br><span class="line">                char_image = d(char_image)</span><br><span class="line">            char_images.append(char_image)</span><br><span class="line">        width, height = self._image.size</span><br><span class="line">        offset = int((width - sum(int(i.size[<span class="number">0</span>] * squeeze_factor)</span><br><span class="line">                                  <span class="keyword">for</span> i <span class="keyword">in</span> char_images[:<span class="number">-1</span>]) -</span><br><span class="line">                      char_images[<span class="number">-1</span>].size[<span class="number">0</span>]) / <span class="number">2</span>)</span><br><span class="line">        <span class="keyword">for</span> char_image <span class="keyword">in</span> char_images:</span><br><span class="line">            c_width, c_height = char_image.size</span><br><span class="line">            mask = char_image.convert(<span class="string">&#x27;L&#x27;</span>).point(<span class="keyword">lambda</span> i: i * <span class="number">1.97</span>)</span><br><span class="line">            self._image.paste(char_image,</span><br><span class="line">                              (offset, int((height - c_height) / <span class="number">2</span>)),</span><br><span class="line">                              mask)</span><br><span class="line">            offset += int(c_width * squeeze_factor)</span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_warp</span>(<span class="params">image, dx_factor=<span class="number">0.3</span>, dy_factor=<span class="number">0.3</span></span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;图像扭曲&quot;&quot;&quot;</span></span><br><span class="line">        width, height = image.size</span><br><span class="line">        dx = width * dx_factor</span><br><span class="line">        dy = height * dy_factor</span><br><span class="line">        x1 = int(random.uniform(-dx, dx))</span><br><span class="line">        y1 = int(random.uniform(-dy, dy))</span><br><span class="line">        x2 = int(random.uniform(-dx, dx))</span><br><span class="line">        y2 = int(random.uniform(-dy, dy))</span><br><span class="line">        warp_image = Image.new(</span><br><span class="line">            <span class="string">&#x27;RGB&#x27;</span>,</span><br><span class="line">            (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))</span><br><span class="line">        warp_image.paste(image, (abs(x1), abs(y1)))</span><br><span class="line">        width2, height2 = warp_image.size</span><br><span class="line">        <span class="keyword">return</span> warp_image.transform(</span><br><span class="line">            (width, height),</span><br><span class="line">            Image.QUAD,</span><br><span class="line">            (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))</span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_offset</span>(<span class="params">image, dx_factor=<span class="number">0.1</span>, dy_factor=<span class="number">0.2</span></span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;图像偏移&quot;&quot;&quot;</span></span><br><span class="line">        width, height = image.size</span><br><span class="line">        dx = int(random.random() * width * dx_factor)</span><br><span class="line">        dy = int(random.random() * height * dy_factor)</span><br><span class="line">        offset_image = Image.new(<span class="string">&#x27;RGB&#x27;</span>, (width + dx, height + dy))</span><br><span class="line">        offset_image.paste(image, (dx, dy))</span><br><span class="line">        <span class="keyword">return</span> offset_image</span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">_rotate</span>(<span class="params">image, angle=<span class="number">25</span></span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;图像旋转&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">return</span> image.rotate(random.uniform(-angle, angle),</span><br><span class="line">                            Image.BILINEAR, expand=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">generate</span>(<span class="params">self, captcha_text=<span class="string">&#x27;&#x27;</span>, fmt=<span class="string">&#x27;PNG&#x27;</span></span>):</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;生成验证码(文字和图片)</span></span><br><span class="line"><span class="string">        :param captcha_text: 验证码文字</span></span><br><span class="line"><span class="string">        :param fmt: 生成的验证码图片格式</span></span><br><span class="line"><span class="string">        :return: 验证码图片的二进制数据</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        self._image = Image.new(<span class="string">&#x27;RGB&#x27;</span>, (self._width, self._height), (<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>))</span><br><span class="line">        self._background()</span><br><span class="line">        self._text(captcha_text, self._fonts,</span><br><span class="line">                   drawings=[<span class="string">&#x27;_warp&#x27;</span>, <span class="string">&#x27;_rotate&#x27;</span>, <span class="string">&#x27;_offset&#x27;</span>])</span><br><span class="line">        self._curve()</span><br><span class="line">        self._noise()</span><br><span class="line">        self._smooth()</span><br><span class="line">        image_bytes = BytesIO()</span><br><span class="line">        self._image.save(image_bytes, format=fmt)</span><br><span class="line">        <span class="keyword">return</span> image_bytes.getvalue()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">pascal_row</span>(<span class="params">n=<span class="number">0</span></span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成毕达哥拉斯三角形（杨辉三角）&quot;&quot;&quot;</span></span><br><span class="line">    result = [<span class="number">1</span>]</span><br><span class="line">    x, numerator = <span class="number">1</span>, n</span><br><span class="line">    <span class="keyword">for</span> denominator <span class="keyword">in</span> range(<span class="number">1</span>, n // <span class="number">2</span> + <span class="number">1</span>):</span><br><span class="line">        x *= numerator</span><br><span class="line">        x /= denominator</span><br><span class="line">        result.append(x)</span><br><span class="line">        numerator -= <span class="number">1</span></span><br><span class="line">    <span class="keyword">if</span> n &amp; <span class="number">1</span> == <span class="number">0</span>:</span><br><span class="line">        result.extend(reversed(result[:<span class="number">-1</span>]))</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        result.extend(reversed(result))</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">random_color</span>(<span class="params">start=<span class="number">0</span>, end=<span class="number">255</span>, opacity=<span class="number">255</span></span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;获得随机颜色&quot;&quot;&quot;</span></span><br><span class="line">    red = random.randint(start, end)</span><br><span class="line">    green = random.randint(start, end)</span><br><span class="line">    blue = random.randint(start, end)</span><br><span class="line">    <span class="keyword">if</span> opacity <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> red, green, blue</span><br><span class="line">    <span class="keyword">return</span> red, green, blue, opacity</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：上面的代码中用到了三个字体文件，字体文件位于<code>polls/fonts</code>目录下，大家可以自行添加字体文件，但是需要注意字体文件的文件名跟上面代码的第45行保持一致。</p>
</blockquote>
<p>接下来，我们先完成提供验证码的视图函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_captcha</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;验证码&quot;&quot;&quot;</span></span><br><span class="line">    captcha_text = gen_random_code()</span><br><span class="line">    request.session[<span class="string">&#x27;captcha&#x27;</span>] = captcha_text</span><br><span class="line">    image_data = Captcha.instance().generate(captcha_text)</span><br><span class="line">    <span class="keyword">return</span> HttpResponse(image_data, content_type=<span class="string">&#x27;image/png&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>注意上面代码中的第4行，我们将随机生成的验证码字符串保存到session中，稍后用户登录时，我们要将保存在session中的验证码字符串和用户输入的验证码字符串进行比对，如果用户输入了正确的验证码才能够执行后续的登录流程，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">login</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    hint = <span class="string">&#x27;&#x27;</span></span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">&#x27;POST&#x27;</span>:</span><br><span class="line">        username = request.POST.get(<span class="string">&#x27;username&#x27;</span>)</span><br><span class="line">        password = request.POST.get(<span class="string">&#x27;password&#x27;</span>)</span><br><span class="line">        <span class="keyword">if</span> username <span class="keyword">and</span> password:</span><br><span class="line">            password = gen_md5_digest(password)</span><br><span class="line">            user = User.objects.filter(username=username, password=password).first()</span><br><span class="line">            <span class="keyword">if</span> user:</span><br><span class="line">                request.session[<span class="string">&#x27;userid&#x27;</span>] = user.no</span><br><span class="line">                request.session[<span class="string">&#x27;username&#x27;</span>] = user.username</span><br><span class="line">                <span class="keyword">return</span> redirect(<span class="string">&#x27;/&#x27;</span>)</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                hint = <span class="string">&#x27;用户名或密码错误&#x27;</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            hint = <span class="string">&#x27;请输入有效的用户名和密码&#x27;</span></span><br><span class="line">    <span class="keyword">return</span> render(request, <span class="string">&#x27;login.html&#x27;</span>, &#123;<span class="string">&#x27;hint&#x27;</span>: hint&#125;)</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>说明</strong>：上面的代码没有对用户名和密码没有进行验证，实际项目中建议使用正则表达式验证用户输入信息，否则有可能将无效的数据交给数据库进行处理或者造成其他安全方面的隐患。</p>
</blockquote>
<p>上面的代码中，我们设定了登录成功后会在session中保存用户的编号（<code>userid</code>）和用户名（<code>username</code>），页面会重定向到首页。接下来我们可以稍微对首页的代码进行调整，在页面的右上角显示出登录用户的用户名。我们将这段代码单独写成了一个名为header.html的HTML文件，首页中可以通过在<code>&lt;body&gt;</code>标签中添加<code>&#123;% include 'header.html' %&#125;</code>来包含这个页面，代码如下所示。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;user&quot;</span>&gt;</span></span><br><span class="line">    &#123;% if request.session.userid %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">span</span>&gt;</span>&#123;&#123; request.session.username &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/logout&quot;</span>&gt;</span>注销<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    &#123;% else %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/login&quot;</span>&gt;</span>登录<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">    &#123;% endif %&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/register&quot;</span>&gt;</span>注册<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>如果用户没有登录，页面会显示登录和注册的超链接；而用户登录成功后，页面上会显示用户名和注销的链接，注销链接对应的视图函数如下所示，URL的映射与之前讲过的类似，不再赘述。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">logout</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;注销&quot;&quot;&quot;</span></span><br><span class="line">    request.session.flush()</span><br><span class="line">    <span class="keyword">return</span> redirect(<span class="string">&#x27;/&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>上面的代码通过session对象<code>flush</code>方法来销毁session，一方面清除了服务器上session对象保存的用户数据，一方面将保存在浏览器cookie中的sessionid删除掉，稍后我们会对如何读写cookie的操作加以说明。</p>
<p>我们可以通过项目使用的数据库中名为<code>django_session</code> 的表来找到所有的session，该表的结构如下所示：</p>
<table>
<thead>
<tr>
<th>session_key</th>
<th>session_data</th>
<th>expire_date</th>
</tr>
</thead>
<tbody><tr>
<td>c9g2gt5cxo0k2evykgpejhic5ae7bfpl</td>
<td>MmI4YzViYjJhOGMyMDJkY2M5Yzg3…</td>
<td>2019-05-25 23:16:13.898522</td>
</tr>
</tbody></table>
<p>其中，第1列就是浏览器cookie中保存的sessionid；第2列是经过BASE64编码后的session中的数据，如果使用Python的<code>base64</code>对其进行解码，解码的过程和结果如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"></span><br><span class="line">base64.b64decode(<span class="string">&#x27;MmI4YzViYjJhOGMyMDJkY2M5Yzg3ZWIyZGViZmUzYmYxNzdlNDdmZjp7ImNhcHRjaGEiOiJzS3d0Iiwibm8iOjEsInVzZXJuYW1lIjoiamFja2ZydWVkIn0=&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>第3列是session的过期时间，session过期后浏览器保存的cookie中的sessionid就会失效，但是数据库中的这条对应的记录仍然会存在，如果想清除过期的数据，可以使用下面的命令。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py clearsessions</span><br></pre></td></tr></table></figure>

<p>Django框架默认的session过期时间为两周（1209600秒），如果想修改这个时间，可以在项目的配置文件中添加如下所示的代码。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置会话的超时时间为1天（86400秒）</span></span><br><span class="line">SESSION_COOKIE_AGE = <span class="number">86400</span></span><br></pre></td></tr></table></figure>

<p>有很多对安全性要求较高的应用都必须在关闭浏览器窗口时让会话过期，不再保留用户的任何信息，如果希望在关闭浏览器窗口时就让会话过期（cookie中的sessionid失效），可以加入如下所示的配置。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置为True在关闭浏览器窗口时session就过期</span></span><br><span class="line">SESSION_EXPIRE_AT_BROWSER_CLOSE = <span class="literal">True</span></span><br></pre></td></tr></table></figure>

<p>如果不希望将session的数据保存在数据库中，可以将其放入缓存中，对应的配置如下所示，缓存的配置和使用我们在后面讲解。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置将会话对象放到缓存中存储</span></span><br><span class="line">SESSION_ENGINE = <span class="string">&#x27;django.contrib.sessions.backends.cache&#x27;</span></span><br><span class="line"><span class="comment"># 配置使用哪一组缓存来保存会话</span></span><br><span class="line">SESSION_CACHE_ALIAS = <span class="string">&#x27;default&#x27;</span></span><br></pre></td></tr></table></figure>

<p>如果要修改session数据默认的序列化方式，可以将默认的<code>JSONSerializer</code>修改为<code>PickleSerializer</code>。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SESSION_SERIALIZER = <span class="string">&#x27;django.contrib.sessions.serializers.PickleSerializer&#x27;</span></span><br></pre></td></tr></table></figure>

<p>接下来，我们就可以限制只有登录用户才能为老师投票，修改后的<code>praise_or_criticize</code>函数如下所示，我们通过从<code>request.session</code>中获取<code>userid</code>来判定用户是否登录。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">praise_or_criticize</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    <span class="keyword">if</span> request.session.get(<span class="string">&#x27;userid&#x27;</span>):</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            tno = int(request.GET.get(<span class="string">&#x27;tno&#x27;</span>))</span><br><span class="line">            teacher = Teacher.objects.get(no=tno)</span><br><span class="line">            <span class="keyword">if</span> request.path.startswith(<span class="string">&#x27;/praise/&#x27;</span>):</span><br><span class="line">                teacher.good_count += <span class="number">1</span></span><br><span class="line">                count = teacher.good_count</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                teacher.bad_count += <span class="number">1</span></span><br><span class="line">                count = teacher.bad_count</span><br><span class="line">            teacher.save()</span><br><span class="line">            data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">20000</span>, <span class="string">&#x27;mesg&#x27;</span>: <span class="string">&#x27;投票成功&#x27;</span>, <span class="string">&#x27;count&#x27;</span>: count&#125;</span><br><span class="line">        <span class="keyword">except</span> (ValueError, Teacher.DoesNotExist):</span><br><span class="line">            data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">20001</span>, <span class="string">&#x27;mesg&#x27;</span>: <span class="string">&#x27;投票失败&#x27;</span>&#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        data = &#123;<span class="string">&#x27;code&#x27;</span>: <span class="number">20002</span>, <span class="string">&#x27;mesg&#x27;</span>: <span class="string">&#x27;请先登录&#x27;</span>&#125;</span><br><span class="line">    <span class="keyword">return</span> JsonResponse(data)</span><br></pre></td></tr></table></figure>

<p>当然，在修改了视图函数后，<code>teachers.html</code>也需要进行调整，用户如果没有登录，就将用户引导至登录页，登录成功再返回到投票页，此处不再赘述。</p>
<h3 id="在视图函数中读写cookie"><a href="#在视图函数中读写cookie" class="headerlink" title="在视图函数中读写cookie"></a>在视图函数中读写cookie</h3><p>下面我们对如何使用cookie做一个更为细致的说明以便帮助大家在Web项目中更好的使用这项技术。Django封装的<code>HttpRequest</code>和<code>HttpResponse</code>对象分别提供了读写cookie的操作。</p>
<p>HttpRequest封装的属性和方法：</p>
<ol>
<li><code>COOKIES</code>属性 - 该属性包含了HTTP请求携带的所有cookie。</li>
<li><code>get_signed_cookie</code>方法 - 获取带签名的cookie，如果签名验证失败，会产生<code>BadSignature</code>异常。</li>
</ol>
<p>HttpResponse封装的方法：</p>
<ol>
<li><code>set_cookie</code>方法 - 该方法可以设置一组键值对并将其最终将写入浏览器。</li>
<li><code>set_signed_cookie</code>方法 - 跟上面的方法作用相似，但是会对cookie进行签名来达到防篡改的作用。因为如果篡改了cookie中的数据，在不知道<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%AF%86%E9%92%A5">密钥</a>和<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%9B%90_(%E5%AF%86%E7%A0%81%E5%AD%A6)">盐</a>的情况下是无法生成有效的签名，这样服务器在读取cookie时会发现数据与签名不一致从而产生<code>BadSignature</code>异常。需要说明的是，这里所说的密钥就是我们在Django项目配置文件中指定的<code>SECRET_KEY</code>，而盐是程序中设定的一个字符串，你愿意设定为什么都可以，只要是一个有效的字符串。</li>
</ol>
<p>上面提到的方法，如果不清楚它们的具体用法，可以自己查阅一下Django的<a target="_blank" rel="noopener" href="https://docs.djangoproject.com/en/2.1/ref/request-response/">官方文档</a>，没有什么资料比官方文档能够更清楚的告诉你这些方法到底如何使用。</p>
<p>刚才我们说过了，激活<code>SessionMiddleware</code>之后，每个<code>HttpRequest</code>对象都会绑定一个session属性，它是一个类似字典的对象，除了保存用户数据之外还提供了检测浏览器是否支持cookie的方法，包括：</p>
<ol>
<li><code>set_test_cookie</code>方法 - 设置用于测试的cookie。</li>
<li><code>test_cookie_worked</code>方法 - 检测测试cookie是否工作。</li>
<li><code>delete_test_cookie</code>方法 - 删除用于测试的cookie。</li>
<li><code>set_expiry</code>方法 - 设置会话的过期时间。</li>
<li><code>get_expire_age</code>/<code>get_expire_date</code>方法 - 获取会话的过期时间。</li>
<li><code>clear_expired</code>方法 - 清理过期的会话。</li>
</ol>
<p>下面是在执行登录之前检查浏览器是否支持cookie的代码。通常情况下，浏览器默认开启了对cookie的支持，但是可能因为某种原因，用户禁用了浏览器的cookie功能，遇到这种情况我们可以在视图函数中提供一个检查功能，如果检查到用户浏览器不支持cookie，可以给出相应的提示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">login</span>(<span class="params">request</span>):</span></span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">&#x27;POST&#x27;</span>:</span><br><span class="line">        <span class="keyword">if</span> request.session.test_cookie_worked():</span><br><span class="line">            request.session.delete_test_cookie()</span><br><span class="line">            <span class="comment"># Add your code to perform login process here</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> HttpResponse(<span class="string">&quot;Please enable cookies and try again.&quot;</span>)</span><br><span class="line">    request.session.set_test_cookie()</span><br><span class="line">    <span class="keyword">return</span> render_to_response(<span class="string">&#x27;login.html&#x27;</span>)</span><br></pre></td></tr></table></figure>

<h3 id="Cookie的替代品"><a href="#Cookie的替代品" class="headerlink" title="Cookie的替代品"></a>Cookie的替代品</h3><p>之前我们说过了，cookie的名声一直都不怎么好，当然我们在实际开发中是不会在cookie中保存用户的敏感信息（如用户的密码、信用卡的账号等）的，而且保存在cookie中的数据一般也会做好编码和签名的工作。对于支持HTML5的浏览器来说，可以使用localStorage和sessionStorage做为cookie的替代方案，相信从名字上你就能听出二者的差别，存储在<code>localStorage</code>的数据可以长期保留；而存储在<code>sessionStorage</code>的数据会在浏览器关闭时会被清除 。关于这些cookie替代品的用法，建议大家查阅<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web">MDN</a>来进行了解。 </p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-django/9-RESTful架构和DRF入门"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/django/9-RESTful%E6%9E%B6%E6%9E%84%E5%92%8CDRF%E5%85%A5%E9%97%A8/"
    >9-RESTful架构和DRF入门.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/django/9-RESTful%E6%9E%B6%E6%9E%84%E5%92%8CDRF%E5%85%A5%E9%97%A8/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/django/">django</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="RESTful架构和DRF入门"><a href="#RESTful架构和DRF入门" class="headerlink" title="RESTful架构和DRF入门"></a>RESTful架构和DRF入门</h2><p>把软件（Software）、平台（Platform）、基础设施（Infrastructure）做成服务（Service）是很多IT企业都一直在做的事情，这就是大家经常听到的SasS（软件即服务）、PasS（平台即服务）和IasS（基础设置即服务）。实现面向服务的架构（SOA）有诸多的方式，包括RPC（远程过程调用）、Web Service、REST等，在技术层面上，SOA是一种<strong>抽象的、松散耦合的粗粒度软件架构</strong>；在业务层面上，SOA的核心概念是“<strong>重用</strong>”和“<strong>互操作</strong>”，它将系统资源整合成可操作的、标准的服务，使得这些资源能够被重新组合和应用。在实现SOA的诸多方案中，REST被认为是最适合互联网应用的架构，符合REST规范的架构也经常被称作RESTful架构。</p>
<h3 id="REST概述"><a href="#REST概述" class="headerlink" title="REST概述"></a>REST概述</h3><p>REST这个词，是<strong>Roy Thomas Fielding</strong>在他2000年的博士论文中提出的，Roy是HTTP协议（1.0和1.1版）的主要设计者、Apache服务器软件主要作者、Apache基金会第一任主席。在他的博士论文中，Roy把他对互联网软件的架构原则定名为REST，即<strong>RE</strong>presentational <strong>S</strong>tate <strong>T</strong>ransfer的缩写，中文通常翻译为“<strong>表现层状态转移</strong>”或“<strong>表述状态转移</strong>”。</p>
<p>这里的“表现层”其实指的是“资源”的“表现层”。所谓资源，就是网络上的一个实体，或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲或一种服务。我们可以用一个URI（统一资源定位符）指向资源，要获取到这个资源，访问它的URI即可，URI就是资源在互联网上的唯一标识。资源可以有多种外在表现形式。我们把资源具体呈现出来的形式，叫做它的“表现层”。比如，文本可以用<code>text/plain</code>格式表现，也可以用<code>text/html</code>格式、<code>text/xml</code>格式、<code>application/json</code>格式表现，甚至可以采用二进制格式；图片可以用<code>image/jpeg</code>格式表现，也可以用<code>image/png</code>格式表现。URI只代表资源的实体，不代表它的表现形式。严格地说，有些网址最后的<code>.html</code>后缀名是不必要的，因为这个后缀名表示格式，属于“表现层”范畴，而URI应该只代表“资源”的位置，它的具体表现形式，应该在HTTP请求的头信息中用<code>Accept</code>和<code>Content-Type</code>字段指定，这两个字段才是对“表现层”的描述。</p>
<p>访问一个网站，就代表了客户端和服务器的一个互动过程。在这个过程中，势必涉及到数据和状态的变化。Web应用通常使用HTTP作为其通信协议，客户端想要操作服务器，必须通过HTTP请求，让服务器端发生“状态转移”，而这种转移是建立在表现层之上的，所以就是“表现层状态转移”。客户端通过HTTP的动词GET、POST、PUT（或PATCH）、DELETE，分别对应对资源的四种基本操作，其中GET用来获取资源，POST用来新建资源（也可以用于更新资源），PUT（或PATCH）用来更新资源，DELETE用来删除资源。</p>
<p>简单的说RESTful架构就是：“每一个URI代表一种资源，客户端通过四个HTTP动词，对服务器端资源进行操作，实现资源的表现层状态转移”。</p>
<p>我们在设计Web应用时，如果需要向客户端提供资源，就可以使用REST风格的URI，这是实现RESTful架构的第一步。当然，真正的RESTful架构并不只是URI符合REST风格，更为重要的是“无状态”和“幂等性”两个词，我们在后面的课程中会为大家阐述这两点。下面的例子给出了一些符合REST风格的URI，供大家在设计URI时参考。</p>
<table>
<thead>
<tr>
<th>请求方法（HTTP动词）</th>
<th>URI</th>
<th>解释</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td><code>/students/</code></td>
<td>获取所有学生</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td><code>/students/</code></td>
<td>新建一个学生</td>
</tr>
<tr>
<td><strong>GET</strong></td>
<td><code>/students/ID/</code></td>
<td>获取指定ID的学生信息</td>
</tr>
<tr>
<td><strong>PUT</strong></td>
<td><code>/students/ID/</code></td>
<td>更新指定ID的学生信息（提供该学生的全部信息）</td>
</tr>
<tr>
<td><strong>PATCH</strong></td>
<td><code>/students/ID/</code></td>
<td>更新指定ID的学生信息（提供该学生的部分信息）</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td><code>/students/ID/</code></td>
<td>删除指定ID的学生信息</td>
</tr>
<tr>
<td><strong>GET</strong></td>
<td><code>/students/ID/friends/</code></td>
<td>列出指定ID的学生的所有朋友</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td><code>/students/ID/friends/ID/</code></td>
<td>删除指定ID的学生的指定ID的朋友</td>
</tr>
</tbody></table>
<h3 id="DRF使用入门"><a href="#DRF使用入门" class="headerlink" title="DRF使用入门"></a>DRF使用入门</h3><p>在Django项目中，如果要实现REST架构，即将网站的资源发布成REST风格的API接口，可以使用著名的三方库<code>djangorestframework</code> ，我们通常将其简称为DRF。</p>
<h4 id="安装和配置DRF"><a href="#安装和配置DRF" class="headerlink" title="安装和配置DRF"></a>安装和配置DRF</h4><p>安装DRF。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install djangorestframework</span><br></pre></td></tr></table></figure>

<p>配置DRF。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">INSTALLED_APPS = [</span><br><span class="line"></span><br><span class="line">    <span class="string">&#x27;rest_framework&#x27;</span>,</span><br><span class="line">    </span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 下面的配置根据项目需要进行设置</span></span><br><span class="line">REST_FRAMEWORK = &#123;</span><br><span class="line">    <span class="comment"># 配置默认页面大小</span></span><br><span class="line">    <span class="comment"># &#x27;PAGE_SIZE&#x27;: 10,</span></span><br><span class="line">    <span class="comment"># 配置默认的分页类</span></span><br><span class="line">    <span class="comment"># &#x27;DEFAULT_PAGINATION_CLASS&#x27;: &#x27;...&#x27;,</span></span><br><span class="line">    <span class="comment"># 配置异常处理器</span></span><br><span class="line">    <span class="comment"># &#x27;EXCEPTION_HANDLER&#x27;: &#x27;...&#x27;,</span></span><br><span class="line">    <span class="comment"># 配置默认解析器</span></span><br><span class="line">    <span class="comment"># &#x27;DEFAULT_PARSER_CLASSES&#x27;: (</span></span><br><span class="line">    <span class="comment">#     &#x27;rest_framework.parsers.JSONParser&#x27;,</span></span><br><span class="line">    <span class="comment">#     &#x27;rest_framework.parsers.FormParser&#x27;,</span></span><br><span class="line">    <span class="comment">#     &#x27;rest_framework.parsers.MultiPartParser&#x27;,</span></span><br><span class="line">    <span class="comment"># ),</span></span><br><span class="line">    <span class="comment"># 配置默认限流类</span></span><br><span class="line">    <span class="comment"># &#x27;DEFAULT_THROTTLE_CLASSES&#x27;: (</span></span><br><span class="line">    <span class="comment">#     &#x27;...&#x27;</span></span><br><span class="line">    <span class="comment"># ),</span></span><br><span class="line">    <span class="comment"># 配置默认授权类</span></span><br><span class="line">    <span class="comment"># &#x27;DEFAULT_PERMISSION_CLASSES&#x27;: (</span></span><br><span class="line">    <span class="comment">#     &#x27;...&#x27;,</span></span><br><span class="line">    <span class="comment"># ),</span></span><br><span class="line">    <span class="comment"># 配置默认认证类</span></span><br><span class="line">    <span class="comment"># &#x27;DEFAULT_AUTHENTICATION_CLASSES&#x27;: (</span></span><br><span class="line">    <span class="comment">#     &#x27;...&#x27;,</span></span><br><span class="line">    <span class="comment"># ),</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="编写序列化器"><a href="#编写序列化器" class="headerlink" title="编写序列化器"></a>编写序列化器</h4><p>前后端分离的开发需要后端为前端、移动端提供API数据接口，而API接口通常情况下都是返回JSON格式的数据，这就需要对模型对象进行序列化处理。DRF中封装了<code>Serializer</code>类和<code>ModelSerializer</code>类用于实现序列化操作，通过继承<code>Serializer</code>类或<code>ModelSerializer</code>类，我们可以自定义序列化器，用于将对象处理成字典，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework <span class="keyword">import</span> serializers </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubjectSerializer</span>(<span class="params">serializers.ModelSerializer</span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        model = Subject</span><br><span class="line">        fields = <span class="string">&#x27;__all__&#x27;</span></span><br></pre></td></tr></table></figure>

<p>上面的代码直接继承了<code>ModelSerializer</code>，通过<code>Meta</code>类的<code>model</code>属性指定要序列化的模型以及<code>fields</code>属性指定需要序列化的模型字段，稍后我们就可以在视图函数中使用该类来实现对<code>Subject</code>模型的序列化。</p>
<h4 id="编写视图函数"><a href="#编写视图函数" class="headerlink" title="编写视图函数"></a>编写视图函数</h4><p>DRF框架支持两种实现数据接口的方式，一种是FBV（基于函数的视图），另一种是CBV（基于类的视图）。我们先看看FBV的方式如何实现数据接口，代码如下所示。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.decorators <span class="keyword">import</span> api_view</span><br><span class="line"><span class="keyword">from</span> rest_framework.response <span class="keyword">import</span> Response</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@api_view((&#x27;GET&#x27;, ))</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_subjects</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    subjects = Subject.objects.all().order_by(<span class="string">&#x27;no&#x27;</span>)</span><br><span class="line">    <span class="comment"># 创建序列化器对象并指定要序列化的模型</span></span><br><span class="line">    serializer = SubjectSerializer(subjects, many=<span class="literal">True</span>)</span><br><span class="line">    <span class="comment"># 通过序列化器的data属性获得模型对应的字典并通过创建Response对象返回JSON格式的数据</span></span><br><span class="line">    <span class="keyword">return</span> Response(serializer.data)</span><br></pre></td></tr></table></figure>

<p>对比上一个章节的使用<code>bpmapper</code>实现模型序列化的代码，使用DRF的代码更加简单明了，而且DRF本身自带了一套页面，可以方便我们查看我们使用DRF定制的数据接口，如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/drf-app.png" alt="img"></p>
<p>直接使用上一节写好的页面，就可以通过Vue.js把上面接口提供的学科数据渲染并展示出来，此处不再进行赘述。</p>
<h4 id="实现老师信息数据接口"><a href="#实现老师信息数据接口" class="headerlink" title="实现老师信息数据接口"></a>实现老师信息数据接口</h4><p>编写序列化器。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SubjectSimpleSerializer</span>(<span class="params">serializers.ModelSerializer</span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        model = Subject</span><br><span class="line">        fields = (<span class="string">&#x27;no&#x27;</span>, <span class="string">&#x27;name&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TeacherSerializer</span>(<span class="params">serializers.ModelSerializer</span>):</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> <span class="title">Meta</span>:</span></span><br><span class="line">        model = Teacher</span><br><span class="line">        exclude = (<span class="string">&#x27;subject&#x27;</span>, )</span><br></pre></td></tr></table></figure>

<p>编写视图函数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@api_view((&#x27;GET&#x27;, ))</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">show_teachers</span>(<span class="params">request: HttpRequest</span>) -&gt; HttpResponse:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        sno = int(request.GET.get(<span class="string">&#x27;sno&#x27;</span>))</span><br><span class="line">        subject = Subject.objects.only(<span class="string">&#x27;name&#x27;</span>).get(no=sno)</span><br><span class="line">        teachers = Teacher.objects.filter(subject=subject).defer(<span class="string">&#x27;subject&#x27;</span>).order_by(<span class="string">&#x27;no&#x27;</span>)</span><br><span class="line">        subject_seri = SubjectSimpleSerializer(subject)</span><br><span class="line">        teacher_seri = TeacherSerializer(teachers, many=<span class="literal">True</span>)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">&#x27;subject&#x27;</span>: subject_seri.data, <span class="string">&#x27;teachers&#x27;</span>: teacher_seri.data&#125;)</span><br><span class="line">    <span class="keyword">except</span> (TypeError, ValueError, Subject.DoesNotExist):</span><br><span class="line">        <span class="keyword">return</span> Response(status=<span class="number">404</span>)</span><br></pre></td></tr></table></figure>

<p>配置URL映射。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">urlpatterns = [</span><br><span class="line">    </span><br><span class="line">    path(<span class="string">&#x27;api/teachers/&#x27;</span>, show_teachers),</span><br><span class="line">    </span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<p>通过Vue.js渲染页面。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>老师信息<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="css">        <span class="comment">/* 此处省略掉层叠样式表 */</span></span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;container&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#123; subject.name &#125;&#125;学科的老师信息<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">hr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h2</span> <span class="attr">v-if</span>=<span class="string">&quot;loaded &amp;&amp; teachers.length == 0&quot;</span>&gt;</span>暂无该学科老师信息<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;teacher&quot;</span> <span class="attr">v-for</span>=<span class="string">&quot;teacher in teachers&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;photo&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">img</span> <span class="attr">:src</span>=<span class="string">&quot;&#x27;/static/images/&#x27; + teacher.photo&quot;</span> <span class="attr">height</span>=<span class="string">&quot;140&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;info&quot;</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">strong</span>&gt;</span>姓名：&#123;&#123; teacher.name &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>性别：&#123;&#123; teacher.sex | maleOrFemale &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">span</span>&gt;</span>出生日期：&#123;&#123; teacher.birth &#125;&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;intro&quot;</span>&gt;</span>&#123;&#123; teacher.intro &#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;comment&quot;</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&quot;</span> @<span class="attr">click.prevent</span>=<span class="string">&quot;vote(teacher, true)&quot;</span>&gt;</span>好评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    (<span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.good_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                    <span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;&quot;</span> @<span class="attr">click.prevent</span>=<span class="string">&quot;vote(teacher, false)&quot;</span>&gt;</span>差评<span class="tag">&lt;/<span class="name">a</span>&gt;</span><span class="symbol">&amp;nbsp;</span><span class="symbol">&amp;nbsp;</span></span><br><span class="line">                    (<span class="tag">&lt;<span class="name">strong</span>&gt;</span>&#123;&#123; teacher.bad_count &#125;&#125;<span class="tag">&lt;/<span class="name">strong</span>&gt;</span>)</span><br><span class="line">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/static/html/subjects.html&quot;</span>&gt;</span>返回首页<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">let</span> app = <span class="keyword">new</span> Vue(&#123;</span></span><br><span class="line"><span class="javascript">            el: <span class="string">&#x27;#container&#x27;</span>,</span></span><br><span class="line">            data: &#123;</span><br><span class="line">                subject: &#123;&#125;,</span><br><span class="line">                teachers: [],</span><br><span class="line"><span class="javascript">                loaded: <span class="literal">false</span></span></span><br><span class="line">            &#125;,</span><br><span class="line">            created() &#123;</span><br><span class="line"><span class="javascript">                fetch(<span class="string">&#x27;/api/teachers/&#x27;</span> + location.search)</span></span><br><span class="line"><span class="javascript">                    .then(<span class="function"><span class="params">resp</span> =&gt;</span> resp.json())</span></span><br><span class="line"><span class="javascript">                    .then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">this</span>.subject = json.subject</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">this</span>.teachers = json.teachers</span></span><br><span class="line">                    &#125;)</span><br><span class="line">            &#125;,</span><br><span class="line">            filters: &#123;</span><br><span class="line">                maleOrFemale(sex) &#123;</span><br><span class="line"><span class="javascript">                    <span class="keyword">return</span> sex? <span class="string">&#x27;男&#x27;</span>: <span class="string">&#x27;女&#x27;</span></span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            methods: &#123;</span><br><span class="line">               vote(teacher, flag) &#123;</span><br><span class="line"><span class="javascript">                    <span class="keyword">let</span> url = flag? <span class="string">&#x27;/praise/&#x27;</span> : <span class="string">&#x27;/criticize/&#x27;</span></span></span><br><span class="line"><span class="javascript">                    url += <span class="string">&#x27;?tno=&#x27;</span> + teacher.no</span></span><br><span class="line"><span class="javascript">                    fetch(url).then(<span class="function"><span class="params">resp</span> =&gt;</span> resp.json()).then(<span class="function"><span class="params">json</span> =&gt;</span> &#123;</span></span><br><span class="line">                        if (json.code === 10000) &#123;</span><br><span class="line">                            if (flag) &#123;</span><br><span class="line">                                teacher.good_count = json.count</span><br><span class="line"><span class="javascript">                            &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line">                                teacher.bad_count = json.count</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h3 id="前后端分离下的用户登录"><a href="#前后端分离下的用户登录" class="headerlink" title="前后端分离下的用户登录"></a>前后端分离下的用户登录</h3><p>之前我们提到过， HTTP是无状态的，一次请求结束连接断开，下次服务器再收到请求，它就不知道这个请求是哪个用户发过来的。但是对于一个Web应用而言，它是需要有状态管理的，这样才能让服务器知道HTTP请求来自哪个用户，从而判断是否允许该用户请求以及为用户提供更好的服务，这个过程就是常说的<strong>会话管理</strong>。</p>
<p>之前我们做会话管理（用户跟踪）的方法是：用户登录成功后，在服务器端通过一个session对象保存用户相关数据，然后把session对象的ID写入浏览器的cookie中；下一次请求时，HTTP请求头中携带cookie的数据，服务器从HTTP请求头读取cookie中的sessionid，根据这个标识符找到对应的session对象，这样就能够获取到之前保存在session中的用户数据。我们刚才说过，REST架构是最适合互联网应用的架构，它强调了HTTP的无状态性，这样才能保证应用的水平扩展能力（当并发访问量增加时，可以通过增加新的服务器节点来为系统扩容）。显然，基于session实现用户跟踪的方式需要服务器保存session对象，在做水平扩展增加新的服务器节点时，需要复制和同步session对象，这显然是非常麻烦的。解决这个问题有两种方案，一种是架设缓存服务器（如Redis），让多个服务器节点共享缓存服务并将session对象直接置于缓存服务器中；另一种方式放弃基于session的用户跟踪，使用<strong>基于token的用户跟踪</strong>。</p>
<p>基于token的用户跟踪是在用户登录成功后，为用户生成身份标识并保存在浏览器本地存储（localStorage、sessionStorage、cookie等）中，这样的话服务器不需要保存用户状态，从而可以很容易的做到水平扩展。基于token的用户跟踪具体流程如下：</p>
<ol>
<li>用户登录时，如果登录成功就按照某种方式为用户生成一个令牌（token），该令牌中通常包含了用户标识、过期时间等信息而且需要加密并生成指纹（避免伪造或篡改令牌），服务器将令牌返回给前端；</li>
<li>前端获取到服务器返回的token，保存在浏览器本地存储中（可以保存在<code>localStorage</code>或<code>sessionStorage</code>中，对于使用Vue.js的前端项目来说，还可以通过Vuex进行状态管理）；</li>
<li>对于使用了前端路由的项目来说，前端每次路由跳转，可以先判断<code>localStroage</code>中有无token，如果没有则跳转到登录页；</li>
<li>每次请求后端数据接口，在HTTP请求头里携带token；后端接口判断请求头有无token，如果没有token以及token是无效的或过期的，服务器统一返回401；</li>
<li>如果前端收到HTTP响应状态码401，则重定向到登录页面。</li>
</ol>
<p>通过上面的描述，相信大家已经发现了，基于token的用户跟踪最为关键是在用户登录成功时，要为用户生成一个token作为用户的身份标识。生成token的方法很多，其中一种比较成熟的解决方案是使用JSON Web Token。</p>
<h4 id="JWT概述"><a href="#JWT概述" class="headerlink" title="JWT概述"></a>JWT概述</h4><p>JSON Web Token通常简称为JWT，它是一种开放标准（RFC 7519）。随着RESTful架构的流行，越来越多的项目使用JWT作为用户身份认证的方式。JWT相当于是三个JSON对象经过编码后，用<code>.</code>分隔并组合到一起，这三个JSON对象分别是头部（header）、载荷（payload）和签名（signature），如下图所示。</p>
<p><img src="http://iubest.gitee.io/pic/json-web-token.png" alt="img"></p>
<ol>
<li><p>头部</p>
 <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;alg&quot;</span>: <span class="string">&quot;HS256&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;typ&quot;</span>: <span class="string">&quot;JWT&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p> 其中，<code>alg</code>属性表示签名的算法，默认是HMAC SHA256（简写成<code>HS256</code>）；<code>typ</code>属性表示这个令牌的类型，JWT中都统一书写为<code>JWT</code>。</p>
</li>
<li><p>载荷</p>
<p> 载荷部分用来存放实际需要传递的数据。JWT官方文档中规定了7个可选的字段：</p>
<ul>
<li><p>iss ：签发人</p>
</li>
<li><p>exp：过期时间</p>
</li>
<li><p>sub：主题</p>
</li>
<li><p>aud：受众</p>
</li>
<li><p>nbf：生效时间</p>
</li>
<li><p>iat：签发时间</p>
</li>
<li><p>jti：编号</p>
<p>除了官方定义的字典，我们可以根据应用的需要添加自定义的字段，如下所示。</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">&quot;sub&quot;</span>: <span class="string">&quot;1234567890&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;nickname&quot;</span>: <span class="string">&quot;jackfrued&quot;</span>,</span><br><span class="line">  <span class="attr">&quot;role&quot;</span>: <span class="string">&quot;admin&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ul>
</li>
<li><p>签名</p>
<p> 签名部分是对前面两部分生成一个指纹，防止数据伪造和篡改。实现签名首先需要指定一个密钥。这个密钥只有服务器才知道，不能泄露给用户。然后，使用头部指定的签名算法（默认是<code>HS256</code>），按照下面的公式产生签名。</p>
 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HS256(base64Encode(header) + <span class="string">&#x27;.&#x27;</span> + base64Encode(payload), secret)</span><br></pre></td></tr></table></figure>

<p> 算出签名以后，把头部、载荷、签名三个部分拼接成一个字符串，每个部分用<code>.</code>进行分隔，这样一个JWT就生成好了。</p>
</li>
</ol>
<h4 id="JWT的优缺点"><a href="#JWT的优缺点" class="headerlink" title="JWT的优缺点"></a>JWT的优缺点</h4><p>使用JWT的优点非常明显，包括：</p>
<ol>
<li>更容易实现水平扩展，因为令牌保存在浏览器中，服务器不需要做状态管理。</li>
<li>更容易防范CSRF攻击，因为在请求头中添加<code>localStorage</code>或<code>sessionStorage</code>中的token必须靠JavaScript代码完成，而不是自动添加到请求头中的。</li>
<li>可以防伪造和篡改，因为JWT有签名，伪造和篡改的令牌无法通过签名验证，会被认定是无效的令牌。</li>
</ol>
<p>当然，任何技术不可能只有优点没有缺点，JWT也有诸多缺点，大家需要在使用的时候引起注意，具体包括：</p>
<ol>
<li>可能会遭受到XSS攻击（跨站脚本攻击），通过注入恶意脚本执行JavaScript代码获取到用户令牌。</li>
<li>在令牌过期之前，无法作废已经颁发的令牌，要解决这个问题，还需要额外的中间层和代码来辅助。</li>
<li>JWT是用户的身份令牌，一旦泄露，任何人都可以获得该用户的所有权限。为了降低令牌被盗用后产生的风险，JWT的有效期应该设置得比较短。对于一些比较重要的权限，使用时应通过其他方式再次对用户进行认证，例如短信验证码等。</li>
</ol>
<h4 id="使用PyJWT"><a href="#使用PyJWT" class="headerlink" title="使用PyJWT"></a>使用PyJWT</h4><p>在Python代码中，可以使用三方库<code>PyJWT</code>生成和验证JWT，下面是安装<code>PyJWT</code>的命令。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install pyjwt</span><br></pre></td></tr></table></figure>

<p>生成令牌。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">payload = &#123;</span><br><span class="line">    <span class="string">&#x27;exp&#x27;</span>: datetime.datetime.utcnow() + datetime.timedelta(days=<span class="number">1</span>),</span><br><span class="line">    <span class="string">&#x27;userid&#x27;</span>: <span class="number">10001</span></span><br><span class="line">&#125;</span><br><span class="line">token = jwt.encode(payload, settings.SECRET_KEY).decode()</span><br></pre></td></tr></table></figure>

<p>验证令牌。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    token = <span class="string">&#x27;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTQ4NzIzOTEsInVzZXJpZCI6MTAwMDF9.FM-bNxemWLqQQBIsRVvc4gq71y42I9m2zt5nlFxNHUo&#x27;</span></span><br><span class="line">    payload = jwt.decode(token, settings.SECRET_KEY)</span><br><span class="line"><span class="keyword">except</span> InvalidTokenError:</span><br><span class="line">    <span class="keyword">raise</span> AuthenticationFailed(<span class="string">&#x27;无效的令牌或令牌已经过期&#x27;</span>)</span><br></pre></td></tr></table></figure>

<p>如果不清楚JWT具体的使用方式，可以先看看第55天的内容，里面提供了完整的投票项目代码的地址。.SECRET_KEY)<br>except InvalidTokenError:<br>    raise AuthenticationFailed(‘无效的令牌或令牌已经过期’)</p>
<p>```</p>
<p>如果不清楚JWT具体的使用方式，可以先看看第55天的内容，里面提供了完整的投票项目代码的地址。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/django/" rel="tag">django</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-interview/IT运维面试问题总结-Linux基础"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/interview/IT%E8%BF%90%E7%BB%B4%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93-Linux%E5%9F%BA%E7%A1%80/"
    >IT运维面试问题总结-Linux基础.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/interview/IT%E8%BF%90%E7%BB%B4%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93-Linux%E5%9F%BA%E7%A1%80/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/interview/">interview</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="IT运维面试问题总结-Linux基础"><a href="#IT运维面试问题总结-Linux基础" class="headerlink" title="IT运维面试问题总结-Linux基础"></a>IT运维面试问题总结-Linux基础</h1><h2 id="Linux基础"><a href="#Linux基础" class="headerlink" title="Linux基础"></a>Linux基础</h2><h3 id="简述Linux主流的发行版？"><a href="#简述Linux主流的发行版？" class="headerlink" title="简述Linux主流的发行版？"></a>简述Linux主流的发行版？</h3><p>Redhat、CentOS、Fedora、SuSE、Debian、Ubuntu、FreeBSD等。</p>
<h3 id="简述Linux启动过程？"><a href="#简述Linux启动过程？" class="headerlink" title="简述Linux启动过程？"></a>简述Linux启动过程？</h3><ul>
<li>⑴ 开机BIOS自检，加载硬盘。</li>
<li>⑵ 读取MBR，MBR引导。</li>
<li>⑶ grub引导菜单(Boot Loader)。</li>
<li>⑷ 加载内核kernel。</li>
<li>⑸ 启动init进程，依据inittab文件设定运行级别。</li>
<li>⑹ init进程，执行rc.sysinit文件。</li>
<li>⑺ 启动内核模块，执行不同级别的脚本程序。</li>
<li>⑻ 执行/etc/rc.d/rc.local。</li>
<li>⑼ 启动tty，进入系统登陆界面。</li>
</ul>
<h3 id="简述Linux删除文件的原理？"><a href="#简述Linux删除文件的原理？" class="headerlink" title="简述Linux删除文件的原理？"></a>简述Linux删除文件的原理？</h3><p>Linux系统是通过link的数量来控制文件删除的，只有当一个文件不存在任何link的时候，这个文件才会被删除。一般来说每个文件两个link计数器来控制：i_count和i_nlink。当一个文件被一个程序占用的时候i_count就加1。当文件的硬链接多一个的时候i_nlink也加1。删除一个文件，就是让这个文件，没有进程占用，同时i_link数量为0。</p>
<h3 id="简述Linux运行级别？"><a href="#简述Linux运行级别？" class="headerlink" title="简述Linux运行级别？"></a>简述Linux运行级别？</h3><ul>
<li>0：关机模式</li>
<li>1：单用户模式&lt;==破解root密码</li>
<li>2：无网络支持的多用户模式</li>
<li>3：有网络支持的多用户模式（文本模式，工作中最常用的模式）</li>
<li>4：保留，未使用</li>
<li>5：有网络支持的X-windows支持多用户模式（桌面）</li>
<li>6：重新引导系统，即重启</li>
</ul>
<h3 id="简述Linux常见目录及其作用？"><a href="#简述Linux常见目录及其作用？" class="headerlink" title="简述Linux常见目录及其作用？"></a>简述Linux常见目录及其作用？</h3><ul>
<li>/（根目录）：Linux文件系统的起点；</li>
<li>boot：存放Linux系统启动做必须的文件；</li>
<li>var：存放经常变换的文件；</li>
<li>home：普通用户的家目录</li>
<li>root：Linux系统的root用户家目录；</li>
<li>bin：存放系统基本的用户命令；</li>
<li>sbin：存放系统基本的管理命令；</li>
<li>use：存放Linux应用程序；</li>
<li>etc：存放Linux系统和各种程序的配置文件。</li>
</ul>
<h3 id="简述Linux操作系统常见的文件系统有？"><a href="#简述Linux操作系统常见的文件系统有？" class="headerlink" title="简述Linux操作系统常见的文件系统有？"></a>简述Linux操作系统常见的文件系统有？</h3><ul>
<li>EXT3</li>
<li>EXT4</li>
<li>XFS</li>
</ul>
<h3 id="简述Linux系统中的buffer和cache区别？"><a href="#简述Linux系统中的buffer和cache区别？" class="headerlink" title="简述Linux系统中的buffer和cache区别？"></a>简述Linux系统中的buffer和cache区别？</h3><p>buffer和cache都是内存中的一块区域，当CPU需要写数据到磁盘时，由于磁盘速度比较慢，所以CPU先把数据存进buffer，然后CPU去执行其他任务，buffer中的数据会定期写入磁盘；当CPU需要从磁盘读入数据时，由于磁盘速度比较慢，可以把即将用到的数据提前存入cache，CPU直接从Cache中读取数据。</p>
<h3 id="简述Linux中inode和block？"><a href="#简述Linux中inode和block？" class="headerlink" title="简述Linux中inode和block？"></a>简述Linux中inode和block？</h3><p>inode节点是一个64字节长的表，表中包含了文件的相关信息，如：字节数、属主UserID、属组GroupID、读写执行权限、时间戳等。在inode节点表中最重要的内容是：磁盘地址表。</p>
<p>文件名存放在目录当中，但Linux系统内部不使用文件名，而是使用inode号码识别文件。对于系统来说文件名只是inode号码便于识别的别称。即Linux文件系统通过把inode和文件名进行关联来查找文件。当需要读取该文件时，文件系统在当前目录表中查找该文件名对应的项，由此得到该文件相对应的inode节点号，通过该inode节点的磁盘地址表把分散存放的文件物理块连接成文件的逻辑结构。</p>
<p>文件是存储在硬盘上的，硬盘的最小存储单位叫做扇区sector，每个扇区存储512字节。操作系统读取硬盘的时候，不会一个个扇区地读取，这样效率太低，而是一次性连续读取多个扇区，即一次性读取一个块block。这种由多个扇区组成的块，是文件存取的最小单位。块的大小，最常见的是4KB，即连续八个sector组成一个block。</p>
<p>即512字节组成一个扇区（sector），多个扇区组成一个块（block），常见的块单位4KB，即连续八个扇区组成一个block。</p>
<blockquote>
<p>一个文件必须占用一个inode，但至少占用一个block。</p>
</blockquote>
<h3 id="简述Linux文件系统修复fsck过程？"><a href="#简述Linux文件系统修复fsck过程？" class="headerlink" title="简述Linux文件系统修复fsck过程？"></a>简述Linux文件系统修复fsck过程？</h3><p>成功修复文件系统的前提是要有两个以上的主文件系统（即两个系统），并保证在修复之前卸载将被修复的文件系统，然后使用命令fsck对受到破坏的文件系统进行修复。</p>
<p>fsck检查文件系统分为5步，每一步检查系统不同部分的连接特性并对上一步进行验证和修改。</p>
<p>检查从超级块开始、然后是分配的磁盘块、路径名、目录的连接性、链接数目以及空闲块链表、inode。</p>
<h3 id="简述Linux中软链接和硬链接的区别？"><a href="#简述Linux中软链接和硬链接的区别？" class="headerlink" title="简述Linux中软链接和硬链接的区别？"></a>简述Linux中软链接和硬链接的区别？</h3><ul>
<li>软链接</li>
</ul>
<p><code>软链接</code>类似于Windows的快捷方式功能的文件，可以快速连接到目标文件或目录。即再创建一个独立的文件，而这个文件会让数据的读取指向它连接的那个文件的文件名。例如，文件A和文件B的inode号码虽然不一样，但是文件A的内容是文件B的路径。读取文件A时，系统会自动将访问者导向文件B。这时，文件A就称为文件B的软链接。</p>
<p>因此，文件A依赖于文件B而存在，如果删除了文件B，打开文件A就会报错。</p>
<ul>
<li>硬链接</li>
</ul>
<p>通过文件系统的inode链接来产生的新的文件名，而不是产生新的文件，称为硬链接。</p>
<p>一般情况下，每个inode号码对应一个文件名，但是Linux允许多个文件名指向同一个inode号码。意味着可以使用不同的文件名访问相同的内容。创建硬链接，源文件与目标文件的inode号码相同，都指向同一个inode。inode信息中的链接数这时就会增加1。</p>
<ul>
<li>当一个文件拥有多个硬链接时，对文件内容修改，会影响到所有其他文件的内容；</li>
<li>删除一个文件名，不影响另一个文件名的访问，删除一个文件名，只会使得inode中的链接数减1。</li>
<li>区别</li>
</ul>
<p>软链接与硬链接最大的区别：软链接是文件A指向文件B的文件名，而不是文件B的inode号码，文件B的inode链接数不会因此发生变化。</p>
<blockquote>
<p>不能对目录做硬链接，但是通过mkdir命令创建一个新目录，通常其硬链接数应该有2个，因为常见的目录本身为1个硬链接，而目录下面的隐藏目录.（点号）是该目录的又一个硬链接，也算是1个连接数。</p>
</blockquote>
<h3 id="简述TCP三次握手，四次断开，及其优点和缺点，同时相对于UDP的差别？"><a href="#简述TCP三次握手，四次断开，及其优点和缺点，同时相对于UDP的差别？" class="headerlink" title="简述TCP三次握手，四次断开，及其优点和缺点，同时相对于UDP的差别？"></a>简述TCP三次握手，四次断开，及其优点和缺点，同时相对于UDP的差别？</h3><p><code>TCP</code>与<code>UDP</code>概念：</p>
<ul>
<li>TCP：传输控制协议，即面向连接；</li>
<li>UDP：用户数据报协议，无连接的，即发送数据之前不需要建立连接</li>
</ul>
<p>TCP与UDP的优缺点上的区别：</p>
<ul>
<li>TCP的优点：</li>
</ul>
<p>可靠，稳定。TCP的可靠体现在TCP在传递数据之前，会有三次握手来建立连接，而且在数据传递时，有确认、窗口、重传、拥塞控制机制，在数据传完后，还会断开连接用来节约系统资源。</p>
<ul>
<li>三次握手：</li>
</ul>
<ol>
<li>第一次握手，主机A向主机B发出一个含同步序列号的标志位的数据段给主机B ，向主机B请求建立连接。通过这个数据段，A向B声明通信请求，以及告知B可用某个序列号作为起始数据段进行响应；</li>
<li>第二次握手，主机B收到主机A的请求后，用一带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应A。通过此数据段，B向A声明已收到A的请求，A可以传输数据了，同时告知A可用某个序列号作为起始数据段进行响应；</li>
<li>第三次握手，主机A收到主机B的数据段后，再发送一个确认应答，确认已收到主机B 的数据段，之后开始正式实际传输数据。</li>
</ol>
<blockquote>
<ul>
<li><code>ACK</code>：TCP报头的控制位之一，对数据进行确认。确认由目的端发出，来告知发送端这个序列号之前的数据段都收到了。比如，确认号为X，则表示前X-1个数据段都收到了。只有当ACK=1时，确认号才有效，当ACK=0时，确认号无效，此时会要求重传数据，保证数据的完整性。</li>
<li><code>SYN</code>：同步序列号，这个标志位只有在TCP建立连接时才会被置1，握手完成后SYN标志位被置0。</li>
</ul>
</blockquote>
<ul>
<li><p>四次断开：</p>
</li>
<li><ul>
<li>1、当主机A完成数据传输后，将控制位FIN置1，提出停止TCP连接的请求；</li>
<li>2、主机B收到FIN后对其作出响应，确认这一方向上的TCP连接将关闭，将ACK置1；</li>
<li>3、主机B再提出反方向的关闭请求，将FIN置1；</li>
<li>4、主机A对主机B的请求进行确认，将ACK置1，双方向的关闭结束。</li>
</ul>
</li>
</ul>
<p>• TCP的缺点：</p>
<p>慢、效率低、占用系统资源高、易被攻击：TCP在传递数据之前，要先建连接，需要消耗时间，而且在数据传递时，确认机制、重传机制、拥塞控制机制等都会消耗大量的时间，而且要在每台设备上维护所有的传输连接。同时，每个连接都会占用系统的CPU、内存等硬件资源。而且，因为TCP有确认机制、三次握手机制，这些也导致TCP容易被人利用，实现DOS、DDOS、CC等攻击。</p>
<blockquote>
<ul>
<li><code>DoS</code>：拒绝服务（Denial of Servic），造成DoS的攻击行为被称为DoS攻击，其目的是使计算机或网络无法提供正常的服务。最常见的DoS攻击有计算机网络带宽攻击和连通性攻击。</li>
<li><code>DDOS</code>：分布式拒绝服务(DDoS:Distributed Denial of Service)，DDoS攻击指借助于客户/服务器技术，将多个计算机联合起来作为攻击平台，对一个或多个目标发动DDoS攻击，从而成倍地提高拒绝服务攻击的威力。</li>
</ul>
</blockquote>
<ul>
<li><p>UDP的优点：</p>
<p>快、比TCP稍安全、没有TCP的握手、确认、窗口、重传、拥塞控制等机制，UDP是一个无状态的传输协议，所以它在传递数据时非常快。没有TCP的这些机制，UDP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的，比如：UDP Flood攻击。</p>
</li>
</ul>
<blockquote>
<p>UDP Flood攻击检测：短时间内向特定目标不断发送 UDP 报文，致使目标系统负担过重而不能处理合法的传输任务，就发生了 UDP Flood。启用 UDP Flood 攻击检测功能时，要求设置一个连接速率阈值，一旦发现保护主机响应的 UDP 连接速率超过该值，防火墙会输出发生 UDP Flood 攻击的告警日志，并且根据用户的配置可以阻止发往该主机的后续连接请求。</p>
</blockquote>
<ul>
<li>UDP的缺点：</li>
</ul>
<p>不可靠、不稳定。因为UDP没有那些可靠的机制，在数据传递时，如果网络质量不好，就会很容易丢包。</p>
<ul>
<li>TCP应用场景：</li>
</ul>
<p>当对网络通讯质量有要求的时候，比如：整个数据要准确无误的传递给对方，要求可靠的应用，比如HTTP、HTTPS、FTP等传输文件的协议，POP、SMTP等邮件传输的协议。</p>
<ul>
<li>UDP应用场景：</li>
</ul>
<p>当对网络通讯质量要求不高的时候，要求网络通讯速度能尽量的快。比如QQ语音、QQ视频、TFTP 。</p>
<h3 id="简述TCP-IP及其主要协议？"><a href="#简述TCP-IP及其主要协议？" class="headerlink" title="简述TCP/IP及其主要协议？"></a>简述TCP/IP及其主要协议？</h3><p>TCP/IP协议是一个协议簇，其中包括很多协议的。</p>
<p>TCP/IP协议包括<code>应用层</code>、<code>传输层</code>、<code>网络层</code>、<code>网络访问层</code>（<code>网络接口层</code>、<code>网际层</code>）。</p>
<ul>
<li><p>应用层：应用程序间沟通的层</p>
</li>
<li><ul>
<li>超文本传输协议(HTTP)：万维网的基本协议；</li>
<li>文件传输(TFTP)：简单文件传输协议；</li>
<li>远程登录(Telnet)：提供远程访问其它主机功能，它允许用户登录internet主机，并在这台主机上执行命令；</li>
<li>网络管理(SNMP)：简单网络管理协议，该协议提供了监控网络设备的方法，以及配置管理、统计信息收集、性能管理及安全管理等；</li>
<li>域名系统(DNS)：域名解析服务，该系统用于在internet中将域名及转换成IP地址；</li>
</ul>
</li>
<li><p>传输层：提供了节点间的数据传送服务，给数据包加入传输数据并把它传输到下一层中，这一层负责传送数据，并且确定数据已被送达并接收。</p>
</li>
<li><ul>
<li>传输控制协议（TCP）</li>
<li>用户数据报协议（UDP）</li>
</ul>
</li>
<li><p>网络层：负责提供基本的数据封包传送功能，让每一个数据包都能够到达目的主机（但不检查是否被正确接收）。</p>
</li>
<li><ul>
<li>Internet协议(IP) ：根据网间报文IP地址，从一个网络通过路由器传到另一网络；</li>
<li>ICMP：Internet控制信息协议(ICMP)；</li>
<li>ARP：地址解析协议(ARP) ——“最不安全的协议”。</li>
<li>RARP：反向地址解析协议(RARP)：</li>
</ul>
</li>
<li><p>网络访问层：又称作主机到网络层(host-to-network)，IP地址与物理地址硬件的映射及IP封装成帧，基于不同硬件类型的网络接口，网络访问层定义了与物理介质的连接。</p>
</li>
</ul>
<h3 id="简述OSI模型及其主要协议？"><a href="#简述OSI模型及其主要协议？" class="headerlink" title="简述OSI模型及其主要协议？"></a>简述OSI模型及其主要协议？</h3><p>OSI模型是一个开放式系统互联参考模型，该模型人为的定义了七层结构。由下至上及其主要作用为：</p>
<ol>
<li>物理层：OSI的物理层规定了通信端点之间的机械特性、电气特性、功能特性以及过程特性，该层为上层协议提供了一个传输数据的物理媒体。该层数据的单位称为比特(bit)。其主要有：EIA/TIA、RS-232、EIA/TIA、RS-449、V.35、RJ-45、fddi令牌环网。</li>
<li>数据链路层：定义了在单个链路上如何传输数据，其主要作用包括：作用包括物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。该层数据的单位称为帧(frame)。其主要有：ARP、RARP、SDLC、HDLC、PPP、STP、帧中继。</li>
<li>网络层：定义了端到端的包传输，定义了能够标识所有结点的逻辑地址，还定义了路由实现的方式和学习路由的方式。为了适应最大传输单元长度小于包长度的传输介质，网络层还定义了如何将一个包分解成更小的包的分段方法。主要负责寻找地址和路由选择，网络层还可以实现拥塞控制、网际互连等功能。该层数据的单位称为数据包(packet)。主要有：IP、IPX、RIP、OSPF。</li>
<li>传输层：主要功能：</li>
</ol>
<ul>
<li>为端到端连接提供传输服务；</li>
<li>这种传输服务分为可靠和不可靠的，其中TCP是典型的可靠传输，而UDP则是不可靠传输；</li>
<li>为端到端连接提供流量控制，差错控制，重新排序，服务质量等管理服务。</li>
</ul>
<p>该层数据的单位称为数据段(segment)。主要有：TCP、UDP、SPX、DCCP、SCTP、RTP、RSVP、PPTP。</p>
<ol>
<li>会话层：他定义了如何开始、控制和结束一个会话，即负责建立和断开通信连接（数据流动的逻辑通路）。主要有：RPC、SQL、NetBIOS。</li>
<li>表示层：定义数据格式及加密。主要负责数据格式的转换，确保一个系统的应用层信息可被另一个系统应用层读取。主要有：加密、ASII、TIFF、JPEG、HTML、PICT。</li>
<li>应用层：与其他计算机进行通讯的一个应用，它是对应应用程序的通信服务的，为应用程序提供服务并规定应用程序中通信相关的细节。主要有：Telnet、HTTP、FTP、WWW、NFS、SMTP。</li>
</ol>
<h3 id="简述IP协议、IP地址？"><a href="#简述IP协议、IP地址？" class="headerlink" title="简述IP协议、IP地址？"></a>简述IP协议、IP地址？</h3><p>IP协议(Internet Protocol)：又称互联网协议，是支持网间互连的数据报协议。它提供网间连接的完善功能，包括IP数据包规定互连网络范围内的IP地址格式。</p>
<p>为了实现连接到互联网上的结点之间的通信，必须为每个结点（入网的计算机）分配一个地址，并且应当保证这个地址是全网唯一的，这便是IP地址。</p>
<p>目前的IP地址（IPv4：IP第4版本）由32个二进制位表示，每8位二进制数为一个整数，中间由小数点间隔，整个IP地址空间有4组8位二进制数，由表示主机所在的网络的地址以及主机在该网络中的标识共同组成。为了便于寻址和层次化的构造网络，IP地址被分为A、B、C、D、E五类，商业应用中只用到A、B、C三类。</p>
<ul>
<li>A类地址：网络标识由第一组8位二进制数表示，网络中的主机标识占3组8位二进制数，网络标识的第一位二进制数取值必须为”0”。A类地址允许有126个网段，每个网络大约允许有1670万台主机，通常分配给拥有大量主机的网络（如主干网）。1.0.0.1－127.255.255.254</li>
<li>B类地址：网络标识由前两组8位二进制数表示，网络中的主机标识占两组8位二进制数，网络标识的前两位二进制数取值必须为”10”。B类地址允许有16384个网段，每个网络允许有65533台主机，适用于结点比较多的网络（如区域网）。128.1.0.1－191.255.255.254</li>
<li>C类地址：网络标识由前3组8位二进制数表示，网络中主机标识占1组8位二进制数，网络标识的前3位二进制数取值必须为”110”。具有C类地址的网络允许有254台主机，适用于结点比较少的网络（如校园网）。192.0.1.1－223.255.255.254</li>
</ul>
<p>为了便于记忆，通常习惯采用4个十进制数来表示一个IP地址，十进制数之间采用句点”.”予以分隔。这种IP地址的表示方法也被称为点分十进制法。</p>
<h3 id="简述静态路由和动态路由及其特点？"><a href="#简述静态路由和动态路由及其特点？" class="headerlink" title="简述静态路由和动态路由及其特点？"></a>简述静态路由和动态路由及其特点？</h3><ul>
<li><code>静态路由</code>：由系统管理员创建的路由，适用于网关数量有限的场合，且网络拓朴结构不经常变化的网络。其缺点是不能动态地适用网络状况的变化，当网络状况变化后需要网络管理员手动修改路由表。</li>
<li><code>动态路由</code>：由路由选择协议动态构建的路由，路由协议之间通过交换各自所拥有的路由信息实时更新路由表的内容。动态路由可以自动学习网络的拓朴结构，并更新路由表。其缺点是路由广播更新信息将占据大量的网络带宽。</li>
</ul>
<h3 id="简述NAT的几种类型，及其原理？"><a href="#简述NAT的几种类型，及其原理？" class="headerlink" title="简述NAT的几种类型，及其原理？"></a>简述NAT的几种类型，及其原理？</h3><p>常见的NAT主要有<code>DNA</code>和<code>SNAT</code>。</p>
<ul>
<li><code>SNAT</code>：指在数据包从网卡发送出去的时候，把数据包中的源地址部分替换为指定的IP。此时，接收方就认为数据包的来源是被替换的那个IP的主机。</li>
<li><code>DNAT</code>：指数据包从网卡发送出去的时候，修改数据包中的目的IP。此时，若访问A，但因此DNAT的存在，所有访问A的数据包的目的IP全部修改为B，那么，实际上访问的是B。</li>
</ul>
<h3 id="简述包过滤防火墙和代理应用防火墙的区别？"><a href="#简述包过滤防火墙和代理应用防火墙的区别？" class="headerlink" title="简述包过滤防火墙和代理应用防火墙的区别？"></a>简述包过滤防火墙和代理应用防火墙的区别？</h3><ul>
<li><code>包过滤防火墙</code>：工作在网络层，根据包头中的源IP地址、目标IP地址、协议类型、端口号进行过滤；</li>
<li><code>代理应用防火墙</code>：工作在应用层，使用代理服务器技术，将内网对外网的访问，变为防火墙对外网的访问，可以对包的内容进行分辨，从而过滤。</li>
</ul>
<blockquote>
<ul>
<li>作者：木二</li>
<li>链接：<a target="_blank" rel="noopener" href="https://www.yuque.com/docs/share/d3dd1e8e-6828-4da7-9e30-6a4f45c6fa8e">https://www.yuque.com/docs/share/d3dd1e8e-6828-4da7-9e30-6a4f45c6fa8e</a></li>
</ul>
</blockquote>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/interview/" rel="tag">interview</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-interview/IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/interview/IT%E8%BF%90%E7%BB%B4%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93-%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E3%80%81%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86%E3%80%81%E8%99%9A%E6%8B%9F%E5%B9%B3%E5%8F%B0%E5%92%8C%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86/"
    >IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/interview/IT%E8%BF%90%E7%BB%B4%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93-%E5%9F%BA%E7%A1%80%E6%9C%8D%E5%8A%A1%E3%80%81%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86%E3%80%81%E8%99%9A%E6%8B%9F%E5%B9%B3%E5%8F%B0%E5%92%8C%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/interview/">interview</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理"><a href="#IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理" class="headerlink" title="IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理"></a>IT运维面试问题总结-基础服务、磁盘管理、虚拟平台和系统管理</h1><h2 id="基础服务"><a href="#基础服务" class="headerlink" title="基础服务"></a>基础服务</h2><h3 id="简述Linux中常见的系统服务，其作用分别是？"><a href="#简述Linux中常见的系统服务，其作用分别是？" class="headerlink" title="简述Linux中常见的系统服务，其作用分别是？"></a>简述Linux中常见的系统服务，其作用分别是？</h3><p>常见的系统服务及其作用有：</p>
<ul>
<li>NTP/Chrony：用于时钟同步；</li>
<li>DHCP：动态主机配置协议，用于自动分配主机地址，默认使用UDP 63端口；</li>
<li>DNS：域名解析，运行在UDP协议之上，默认使用53端口；</li>
<li>NFS：网络文件系统，依赖于RCP协议，其基本原则是“容许不同的客户端及服务端通过一组RPC分享相同的文件系统”，它是独立于操作系统，容许不同硬件及操作系统的系统共同进行文件的分享。</li>
<li>Postfix：邮件服务；</li>
<li>rsync：远程数据备份服务。</li>
<li>VPN：虚拟专用网。</li>
</ul>
<blockquote>
<p>更多服务参考：<a target="_blank" rel="noopener" href="http://c.biancheng.net/view/1059.html%E3%80%82">http://c.biancheng.net/view/1059.html。</a></p>
</blockquote>
<h3 id="简述FTP主要的工作模式？"><a href="#简述FTP主要的工作模式？" class="headerlink" title="简述FTP主要的工作模式？"></a>简述FTP主要的工作模式？</h3><p>FTP工作模式是以服务端角度来区分，有主动模式和被动模式。</p>
<ul>
<li>主动模式是指由FTP服务端主动向客户端发起连接，服务端端口号为20（用于传输）和21（用于控制），即20端口向客户端的一个大于1024的随机端口传输数据；</li>
<li>被动模式是指由FTP客户端向服务端发起连接，服务端采用随机端口等待客户端的随机端口来访问，从而传输数据。</li>
</ul>
<h3 id="简述FTP两种登录方式以及两种传输模式？"><a href="#简述FTP两种登录方式以及两种传输模式？" class="headerlink" title="简述FTP两种登录方式以及两种传输模式？"></a>简述FTP两种登录方式以及两种传输模式？</h3><ul>
<li>FTP有两种登录方式：匿名登录和授权登录。</li>
</ul>
<p>使用匿名登录时，用户名为：anonymous，密码为：任何合法email地址；使用授权登录时，用户名为用户在远程FTP系统中的用户帐号，密码为用户在远程系统中的用户密码。</p>
<p><code>区别</code>：使用匿名登录只能访问FTP目录下的资源，默认配置下只能下载；而授权登录访问的权限大于匿名登录，且上载、下载均可。</p>
<ul>
<li>FTP文件传输有两种文件传输模式：ASCII模式和binary模式。</li>
</ul>
<p>ASCII模式用来传输文本文件；其他文件的传输使用binary模式。</p>
<h3 id="简述DHCP的流程？"><a href="#简述DHCP的流程？" class="headerlink" title="简述DHCP的流程？"></a>简述DHCP的流程？</h3><p>新节点通过DHCP获取地址信息的主要流程有如下四个过程：</p>
<p>1、寻找DHCP Server</p>
<p>客户机第一次登录网络的时，向网络上发出一个DHCPDISCOVER广播（包中包含客户机的MAC地址和计算机名等信息）。其源地址为0.0.0.0，目标地址为255.255.255.255。</p>
<p>2、提供IP地址租用</p>
<p>服务端监听到客户机发出的DHCP discover广播后，从剩余地址池中选择最前面的空置IP，连同其它TCP/IP设定，通过广播方式响应给客户端一个DHCP OFFER数据包（包中包含IP地址、子网掩码、地址租期等信息）。源IP地址为DHCP Server的IP地址，目标地址为255.255.255.255。同时，DHCP Server为此客户保留它提供的IP地址，从而不会为其他DHCP客户分配此IP地址。</p>
<p>3、接受IP租约</p>
<p>客户机挑选最先响应的DHCP OFFER（一般是最先到达的那个），同时向网络广播DHCP REQUEST数据包（包中包含客户端的MAC地址、接受的租约中的IP地址、提供此租约的DHCP服务器地址等），声明将接受某一台服务器提供的IP地址。此时，由于还没有得到DHCP Server的最后确认，客户端仍然使用0.0.0.0为源IP地址，255.255.255.255为目标地址进行广播。</p>
<p>4、租约确认</p>
<p>服务端接收到客户端的DHCP REQUEST之后，会广播返回给客户机一个DHCP ACK消息包，表明已经接受客户机的选择，并将这一IP地址的合法租用以及其他的配置信息都放入该广播包发给客户机。</p>
<p>客户机在接收到DHCP ACK广播后，会向网络发送三个针对此IP地址的ARP解析请求以执行冲突检测，查询网络上有没有其它机器使用该IP地址；如果发现该IP地址已经被使用，客户机会发出一个DHCP DECLINE数据包给DHCP Server，拒绝此IP地址租约，并重新发送DHCP discover信息。此时，在DHCP服务器管理控制台中，会显示此IP地址为BAD_ADDRESS。</p>
<p>如果网络上没有其它主机使用此IP地址，则客户机的TCP/IP使用租约中提供的IP地址完成初始化，从而可以和其他网络中的主机进行通讯。</p>
<h3 id="简述DNS查询可能需要哪些过程？"><a href="#简述DNS查询可能需要哪些过程？" class="headerlink" title="简述DNS查询可能需要哪些过程？"></a>简述DNS查询可能需要哪些过程？</h3><p>通常DNS查询有如下过程，任何一过程查询成功则返回查询结果，不再进行下一步查询：</p>
<ul>
<li>1、用户输入网址，优先调取本地hosts查询记录；</li>
<li>2、使用本地dns缓存查询记录；</li>
<li>3、使用网络设置的主dns查询记录；</li>
<li>4、使用dns服务器中的缓存；</li>
<li>5、dns服务器转发查询，转发至上一级ISP DNS服务器，依次循环；</li>
<li>6、若dns服务器未配置转发查询，则将查询需求发至13台根dns；</li>
<li>7、返回查询IP结果给客户端。</li>
</ul>
<h3 id="简述DNS常见的服务器角色类型？"><a href="#简述DNS常见的服务器角色类型？" class="headerlink" title="简述DNS常见的服务器角色类型？"></a>简述DNS常见的服务器角色类型？</h3><ul>
<li>缓冲域名服务器</li>
<li>主域名服务器</li>
<li>从域名服务器</li>
</ul>
<h3 id="简述NFS文件系统及其作用？"><a href="#简述NFS文件系统及其作用？" class="headerlink" title="简述NFS文件系统及其作用？"></a>简述NFS文件系统及其作用？</h3><p>网络文件系统是应用层的一种应用服务，它主要应用于Linux和Linux系统、Linux和Unix系统之间的文件或目录的共享。对于用户而言可以通过NFS方便的访问远地的文件系统，使之成为本地文件系统的一部分。采用NFS之后省去了登录的过程，方便了用户访问系统资源。</p>
<h3 id="简述Samba作用及其使用场景？"><a href="#简述Samba作用及其使用场景？" class="headerlink" title="简述Samba作用及其使用场景？"></a>简述Samba作用及其使用场景？</h3><p>Samba是在Linux上实现SMB协议的一个免费软件，由服务器及客户端程序构成。SMB（Server Messages Block，信息服务块）是一种在局域网上共享文件和打印机的一种通信协议，它为局域网内的不同计算机之间提供文件及打印机等资源的共享服务。SMB协议是客户机/服务器型协议，客户机通过该协议可以访问服务器上的共享文件系统、打印机及其他资源。主要用于windows与Linux之间的文件共享。</p>
<h3 id="简述VPN概念以及常见的类型？"><a href="#简述VPN概念以及常见的类型？" class="headerlink" title="简述VPN概念以及常见的类型？"></a>简述VPN概念以及常见的类型？</h3><p>VPN是指在公共的网络上建立专用网络的技术，但是两个节点间并没有物理上的专用的端到端链路，而是通过广域网或者运营商提供的网络平台之上的逻辑网络，用户数据在逻辑链路中传输，同时VPN采用身份验证和加密技术，充分保证了安全性。常见的VPN有：IPSec VPN、PPTP VPN、L2TP VPN、SSL VPN。</p>
<h3 id="简述YUM服务工作步骤？"><a href="#简述YUM服务工作步骤？" class="headerlink" title="简述YUM服务工作步骤？"></a>简述YUM服务工作步骤？</h3><p>客户端在通过yum安装软件时，会先访问repo仓库，下载仓库的元数据，根据元数据去查询所需要的rpm及其各种依赖关系。之后再在仓库进行相关下载，并自动解决rpm包的依赖关系。同时repo仓库应该是一个文件服务器，一般linux主机在下载过元数据的同时会将其保留在缓存中，以便后续使用。本质上是将底层的物理硬盘抽象的封装起来，然后以逻辑卷的方式呈现给上层应用。</p>
<h2 id="磁盘管理"><a href="#磁盘管理" class="headerlink" title="磁盘管理"></a>磁盘管理</h2><h3 id="简述LVM概念及其特点？"><a href="#简述LVM概念及其特点？" class="headerlink" title="简述LVM概念及其特点？"></a>简述LVM概念及其特点？</h3><p>LVM是对磁盘分区进行管理的一种机制，建立在硬盘和分区之上的一个逻辑层，用来提高磁盘管理的灵活性。通过LVM可将若干个磁盘分区连接为一个整块的卷组(Volume Group)，形成一个存储池。可以在卷组上随意创建逻辑卷(Logical Volumes)，并进一步在逻辑卷上创建文件系统，与直接使用物理存储在管理上相比，提供了更好灵活性。</p>
<ul>
<li><p>设计概念</p>
</li>
<li><ul>
<li>物理存储介质（The physical media）：LVM存储介质可以是磁盘分区、整个磁盘、RAID阵列或SAN磁盘，设备必须初始化为LVM物理卷，才能与LVM结合使用；</li>
<li>物理卷PV（physical volume）：物理卷就是LVM的基本存储逻辑块，但和基本的物理存储介质（如分区、磁盘等）比较，却包含有与LVM相关的管理参数，创建物理卷它可以用硬盘分区，也可以用硬盘本身；</li>
<li>卷组VG（Volume Group）：一个LVM卷组由一个或多个物理卷组成；</li>
<li>逻辑卷LV（logical volume）：LV建立在VG之上，可以在LV之上建立文件系统；</li>
<li>PE（physical extents）：PV物理卷中可以分配的最小存储单元，PE的大小是可以指定的，默认为4MB；</li>
<li>LE（logical extent）：LV逻辑卷中可以分配的最小存储单元，在同一个卷组中，LE的大小和PE是相同的，并且一一对应。</li>
</ul>
</li>
<li><p>特点</p>
</li>
<li><ul>
<li>在从卷组中移除一个磁盘的时候必须使用reducevg命令，有一定的限制：这个命令要求root权限，并且不允许在快照卷组中使用。</li>
<li>当卷组中的一个磁盘损坏时，整个卷组都会受到影响。</li>
<li>因为加入了额外的操作，存储性能受到影响。</li>
<li>可以在系统运行的状态下动态的扩展文件系统的大小。</li>
<li>文件系统可以跨多个磁盘，因此文件系统大小不会受物理磁盘的限制。</li>
<li>可以增加新的磁盘到LVM的存储池中。</li>
<li>可以以镜像的方式冗余重要的数据到多个物理磁盘。</li>
<li>可以方便的导出整个卷组到另外一台机器。</li>
<li>优点</li>
<li>缺点</li>
</ul>
</li>
</ul>
<h3 id="简述RAID0、RAID1、RAID5原理及特点、使用场景？"><a href="#简述RAID0、RAID1、RAID5原理及特点、使用场景？" class="headerlink" title="简述RAID0、RAID1、RAID5原理及特点、使用场景？"></a>简述RAID0、RAID1、RAID5原理及特点、使用场景？</h3><p>RAID通常可以把硬盘整合成一个大磁盘，然后在大磁盘上再分区，提高数据量利用率、冗余性，根据其特点不同，常见的有RAID0、RADI1、RAID5。</p>
<p>RAID 0：指由多个盘组合成逻辑上的一个盘。</p>
<ul>
<li>优点：读写快，容量利用率最高。</li>
<li>缺点：没有冗余，任何一块磁盘失效将影响到所有数据。</li>
</ul>
<p>RAID 1：偶数盘，进行镜像。</p>
<ul>
<li>优点：最高的冗余性。</li>
<li>缺点：浪费资源，成本高，数据利用率低。</li>
</ul>
<p>RAID 5：奇数盘，至少3块磁盘。分布式奇偶校验的独立磁盘结构，它的奇偶校验码存在于所有磁盘上 任何一个硬盘损坏，都可以根据其它硬盘上的校验位来重建损坏的数据。</p>
<ul>
<li>优点：实现数据一定程度的冗余，同时也提升数据的读写性能。</li>
<li>缺点：构建此模式需要一定数量的磁盘。</li>
</ul>
<p>冗余从好到坏：RAID 1 &gt; RAID 10 &gt; RAID 5 &gt; RAID 0</p>
<p>性能从好到坏：RAID 0 &gt; RAID 10 &gt; RAID 5 &gt; RAID 1</p>
<p>成本从低到高：RAID 0 &gt; RAID 5 &gt; RAID 1 &gt; RAID 10</p>
<h3 id="简述iSCSI存储及其优点？"><a href="#简述iSCSI存储及其优点？" class="headerlink" title="简述iSCSI存储及其优点？"></a>简述iSCSI存储及其优点？</h3><p>iSCSI是Internet小型计算机系统接口，是一个基于TCP/IP的协议，用于通过IP网络仿真SCSI高性能本地存储总线，从而为远程存储设备提供数据传输和管理。iSCSI跨本地和广域网，通过分布式服务器和数组提供独立于位置的数据存储检索。</p>
<p>iSCSI优点：</p>
<ul>
<li>使用SAN摆脱了本地布线限制，促进了本地或远程数据中心的存储整合；</li>
<li>iSCSI结构是逻辑性的，仅使用软件配置来进行新的存储分配，无需其他电缆和物理磁盘；</li>
<li>iSCSI使用多个远程数据中心简化了数据复制、迁移和灾难恢复。</li>
</ul>
<h3 id="简述文件存储、块存储、对象存储？"><a href="#简述文件存储、块存储、对象存储？" class="headerlink" title="简述文件存储、块存储、对象存储？"></a>简述文件存储、块存储、对象存储？</h3><p><code>文件存储</code>：允许将数据组织为传统的文件系统。数据保存在一个文件中，该文件具有名称和一些相关的元数据，例如修改时间戳、所有者和访问权限。提供基于文件的存储使用目录和子目录的层次结构来组织文件的存储方式。</p>
<p><code>块存储</code>：块存储提供了一个像硬盘驱动器一样工作的存储卷，组织成大小相同的块。通常，要么操作系统用文件系统格式化基于块的存储卷，要么应用程序(如数据库)直接访问它来存储数据。</p>
<p><code>对象存储</code>：对象存储允许将任意数据和元数据存储为一个单元，并在平面存储池中标记为惟一标识符。使用API存储和检索数据，而不是将数据作为块或在文件系统层次结构中访问。</p>
<h2 id="虚拟平台"><a href="#虚拟平台" class="headerlink" title="虚拟平台"></a>虚拟平台</h2><h3 id="简述什么是云计算及其基本特征？"><a href="#简述什么是云计算及其基本特征？" class="headerlink" title="简述什么是云计算及其基本特征？"></a>简述什么是云计算及其基本特征？</h3><p>云计算是一种采用按量付费的模式，基于虚拟化技术，将相应计算资源（如网络、存储等）池化后，提供便捷的、高可用的、高扩展性的、按需的服务（如计算、存储、应用程序和其他 IT 资源）。</p>
<p>云计算通常有如下基本特征：</p>
<ul>
<li>自主服务：可按需的获取云端的相应资源（主要指公有云）；</li>
<li>网路访问：可随时随地使用任何联网终端设备接入云端从而使用相应资源。</li>
<li>资源池化：</li>
<li>快速弹性：可方便、快捷地按需获取和释放计算资源。</li>
<li>按量计费：</li>
</ul>
<h3 id="简述云计算常见部署模式？"><a href="#简述云计算常见部署模式？" class="headerlink" title="简述云计算常见部署模式？"></a>简述云计算常见部署模式？</h3><ul>
<li>私有云：云平台资源只给某个单位、或某部分用户内部使用。</li>
<li>公有云：云平台资源开放给社会公众服务。</li>
<li>社区云：云平台资源给几个固定的单位内使用。</li>
<li>混合云：两个或两个以上不同类型的云平台。</li>
</ul>
<h3 id="简述云计算三种服务模式？"><a href="#简述云计算三种服务模式？" class="headerlink" title="简述云计算三种服务模式？"></a>简述云计算三种服务模式？</h3><ul>
<li><code>IaaS</code>：基础设施即服务，云服务商将IT系统的基础设施（如计算资源、存储资源、网络资源）池化后作为服务进行售卖；</li>
<li><code>PaaS</code>：平台即服务，云服务商将IT系统的平台软件层（数据库、OS、中间件、运行库）作为服务进行售卖；</li>
<li><code>SaaS</code>：软件即服务，云服务商将IT系统的应用软件层作为服务进行售卖。</li>
</ul>
<h3 id="简述云计算和虚拟化的区别？"><a href="#简述云计算和虚拟化的区别？" class="headerlink" title="简述云计算和虚拟化的区别？"></a>简述云计算和虚拟化的区别？</h3><ul>
<li>云计算：IT能力服务化，按需使用，按量计费，多租户隔离，是一个系统的轻量级管理控制面。</li>
<li>虚拟化：环境隔离，资源复用，降低隔离损耗，提升运行性能，提供高级虚拟化特性。</li>
</ul>
<p>虚拟化是实现云计算的技术支撑之一，但并非云计算的核心关注点。</p>
<h3 id="简述私有云相对公有云有哪些优势？"><a href="#简述私有云相对公有云有哪些优势？" class="headerlink" title="简述私有云相对公有云有哪些优势？"></a>简述私有云相对公有云有哪些优势？</h3><ul>
<li>数据安全性更高；</li>
<li>可节省上云迁移过程中的大量成本；</li>
<li>业务快速部署，缩短业务周期；</li>
<li>降低企业成本，自主可控。</li>
</ul>
<h3 id="简述什么是KVM？"><a href="#简述什么是KVM？" class="headerlink" title="简述什么是KVM？"></a>简述什么是KVM？</h3><p>KVM指基于内核的虚拟机（Kernel-based Virtual Machine），它是一个Linux的一个内核模块，该内核模块使得Linux变成了一个 Hypervisor，从而实现虚拟化：</p>
<ul>
<li>它由 Quramnet 开发，该公司于 2008年被 Red Hat 收购。</li>
<li>它支持 x86 (32 and 64 位)、s390、Powerpc 等 CPU。</li>
<li>它从 Linux 2.6.20 起就作为一模块被包含在 Linux 内核中。</li>
<li>它需要支持虚拟化扩展的 CPU。</li>
<li>它是完全开源的。</li>
</ul>
<h2 id="系统管理"><a href="#系统管理" class="headerlink" title="系统管理"></a>系统管理</h2><h3 id="简述Rsync及其特点？"><a href="#简述Rsync及其特点？" class="headerlink" title="简述Rsync及其特点？"></a>简述Rsync及其特点？</h3><p>Rsync是Linux系统中的数据镜像备份工具，通过rsync可以将本地系统数据通过网络备份到任何远程主机上。rysnc不仅仅能对不同位置的文件和目录进行同步，还可以差异计算，压缩传输文件来最小化数据传输，和cp命令相比，rysnc的优势在于高效的差异算法。并且，rysnc还支持网络数据传输，在复制文件的同时，会把源端与目的端的文件进行比较，只有当文件不一样的时候在进行复制。具有以下特性：</p>
<ul>
<li>可以镜像保存整个目录树和文件系统。</li>
<li>可以同步增量数据，文件传输效率高，同步时间短。</li>
<li>可以保留原有文件的权限、时间等属性。</li>
<li>加密传输数据，保证了数据的安全性。</li>
</ul>
<h3 id="简述iptables规则工作过程？"><a href="#简述iptables规则工作过程？" class="headerlink" title="简述iptables规则工作过程？"></a>简述iptables规则工作过程？</h3><p>iptables防火墙是一层层过滤的，实际是按照配置规则的顺序从上到下，从前到后进行过滤的。</p>
<p>如果匹配上了规则，即明确表明是阻止还是通过，此时数据包就不能向下匹配新规则了。</p>
<p>如果所有规则中没有明确表明是阻止还是通过这个数据包，也就是没有匹配上规则，向下进行匹配，直到匹配默认规则得到明确的阻止还是通过。</p>
<p>防火墙的默认规则是对应链的所有的规则执行完才会执行的，即最后执行的规则。</p>
<h3 id="简述iptables有几个链、表及每个表的作用？"><a href="#简述iptables有几个链、表及每个表的作用？" class="headerlink" title="简述iptables有几个链、表及每个表的作用？"></a>简述iptables有几个链、表及每个表的作用？</h3><p>iptables有5个链：<code>PREROUTING</code>、<code>INPUT</code>、<code>FORWARD</code>、<code>OUTPUT</code>、<code>POSTROUTING</code>。</p>
<p>iptables有4个表：<code>Filter</code>、<code>NAT</code>、<code>Mangle</code>、<code>RAW</code>。</p>
<ul>
<li><p><code>Filter</code>：主要和主机自身有关，真正负责主机防火墙功能（过滤流入流出主机的数据包）。filter表是iptables默认使用的表。filter定义了三个链（chains）：</p>
</li>
<li><ul>
<li>INPUT：负责过滤所有目标地址是本机地址的数据包，通俗的讲，就是过滤进入主机的数据包</li>
<li>FORWARD：负责转发流经主机的数据包。起转发的作用，和nat关系很大，</li>
<li>OUTPUT：处理所有源地址是本机地址的数据包，通俗的讲，就是处理从主机发出去的数据包</li>
</ul>
</li>
</ul>
<p>对于filter表的控制是实现本机防火墙功能的重要手段，特别是对INPUT链的控制。</p>
<ul>
<li><p><code>nat</code>：负责网络地址转换，即来源与目的ip地址的port的转换，一般用于局域网共享上网或特殊的端口转换服务相关，nat功能就相当于网络的acl控制。。nat定义了三个链（chains）：</p>
</li>
<li><ul>
<li>OUTPUT：和主机发出去的数据包有关，改变主机发出数据包的目标地址。</li>
<li>PREROUTING：在数据包到达防火墙时进行路由判断之前执行的规则。作用时改变数据包的目的地址，目的端口等。</li>
<li>POSTROUTING：在数据包离开防火墙时进行路由判断之后执行的规则，作用改变数据包的源地址，源端口等。</li>
</ul>
</li>
<li><p><code>RAW</code>：RAW表只使用在PREROUTING链和OUTPUT链上，因为优先级最高，从而可以对收到的数据包在连接跟踪前进行处理。一但用户使用了RAW表，在某个链上，RAW表处理完后，将跳过NAT表和 ip_conntrack处理，即不再做地址转换和数据包的链接跟踪处理了。RAW表可以应用在那些不需要做nat的情况下，以提高性能。如大量访问的web服务器，可以让80端口不再让iptables做数据包的链接跟踪处理，以提高用户的访问速度。</p>
</li>
<li><p><code>Mangle</code>：实现流量整形，主要用于修改数据包的ToS( Type of Service ，服务类型）、TTL(Time toLive，生存周期）以及为数据包设置 Mark 标记，以实现 QoS(Quality of Service，服务质量）调整以及策略路由等应用。</p>
</li>
</ul>
<h3 id="简述iptables各个表的优先级？"><a href="#简述iptables各个表的优先级？" class="headerlink" title="简述iptables各个表的优先级？"></a>简述iptables各个表的优先级？</h3><p>4个表的优先级由高到低的顺序为：raw–&gt;mangle–&gt;nat–&gt;filter。</p>
<h3 id="简述iptables处理经过的数据包的流程？"><a href="#简述iptables处理经过的数据包的流程？" class="headerlink" title="简述iptables处理经过的数据包的流程？"></a>简述iptables处理经过的数据包的流程？</h3><p>iptables利用表和链处理每个经过的数据包，具体流程（步骤）如下：</p>
<ul>
<li>1、数据包到达网络接口，比如 eth0。</li>
<li>2、进入 raw 表的 PREROUTING 链，这个链的作用是在连接跟踪之前处理数据包。</li>
<li>3、如果进行了连接跟踪，则进行处理。</li>
<li>4、进入 mangle 表的 PREROUTING 链，在此可以修改数据包，比如 TOS 等。</li>
<li>5、进入 nat 表的 PREROUTING 链，可以在此做DNAT，但不做过滤。</li>
<li>6、决定路由，看是交给本地主机还是转发给其它主机，即决定是否继续往内还是往外。</li>
</ul>
<p>到了这里需要分<code>两种不同</code>的情况进行讨论了。</p>
<ul>
<li><p>若数据包决定要转发给其它主机，这时候它会依次经过：</p>
</li>
<li><ul>
<li>1、进入 mangle 表的 FORWARD 链，这里是在第一次路由（即步骤6）决定之后，在进行最后的路由决定之前，仍然可以对数据包进行某些修改。</li>
<li>2、进入 filter 表的 FORWARD 链，这里可以对所有转发的数据包进行过滤。</li>
<li>3、进入 mangle 表的 POSTROUTING 链，这里将完成了所有的路由决定，但数据包仍然在本地主机，还可以进行某些修改。</li>
<li>4、进入 nat 表的 POSTROUTING 链，这里一般都是用来做 SNAT ，不在这里进行过滤。</li>
<li>5、进入出去的网络接口，然后进行发送。</li>
</ul>
</li>
<li><p>另一种情况是，数据包就是发给本地主机的，那么它会依次穿过：</p>
</li>
<li><ul>
<li>1、进入 mangle 表的 INPUT 链，这里是在第一次路由（即步骤6）决定之后，在进行最后的路由决定之前，仍然可以对数据包进行某些修改。</li>
<li>2、进入 filter 表的 INPUT 链，这里可以对流入的所有数据包进行过滤，无论它来自哪个网络接口。</li>
<li>3、交给本地主机的应用程序进行处理。</li>
<li>4、处理完毕后进行路由决定，看该往那里发出。</li>
<li>5、进入 raw 表的 OUTPUT 链，这里是在连接跟踪处理本地的数据包之前。</li>
<li>6、连接跟踪对本地的数据包进行处理。</li>
<li>7、进入 mangle 表的 OUTPUT 链，这里可以修改数据包，但不做过滤。</li>
<li>8、进入 nat 表的 OUTPUT 链，可以对防火墙自己发出的数据做 NAT 。</li>
<li>9、再次进行路由决定。</li>
<li>10、进入 filter 表的 OUTPUT 链，可以对本地出去的数据包进行过滤。</li>
<li>11、进入 mangle 表的 POSTROUTING 链，同上一种情况的第9步。</li>
<li>12、进入 nat 表的 POSTROUTING 链，同上一种情况的第10步。</li>
<li>13、进入出去的网络接口，然后进行发送。</li>
</ul>
</li>
</ul>
<blockquote>
<ul>
<li>作者：木二</li>
<li>链接：<a target="_blank" rel="noopener" href="https://www.yuque.com/docs/share/d3dd1e8e-6828-4da7-9e30-6a4f45c6fa8e">https://www.yuque.com/docs/share/d3dd1e8e-6828-4da7-9e30-6a4f45c6fa8e</a></li>
</ul>
</blockquote>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/interview/" rel="tag">interview</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-k8s/Jenkis pipeline编写podTemplate实现和k8s对接"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/Jenkis%20pipeline%E7%BC%96%E5%86%99podTemplate%E5%AE%9E%E7%8E%B0%E5%92%8Ck8s%E5%AF%B9%E6%8E%A5/"
    >Jenkis pipeline编写podTemplate实现和k8s对接.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/Jenkis%20pipeline%E7%BC%96%E5%86%99podTemplate%E5%AE%9E%E7%8E%B0%E5%92%8Ck8s%E5%AF%B9%E6%8E%A5/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Jenkis-pipeline编写podTemplate实现和k8s对接"><a href="#Jenkis-pipeline编写podTemplate实现和k8s对接" class="headerlink" title="Jenkis pipeline编写podTemplate实现和k8s对接"></a>Jenkis pipeline编写podTemplate实现和k8s对接</h1><blockquote>
<p>在完成前文的jenkins server 在k8s环境部署之后，本文我们来测试在k8s集群环境中的jenkins pipeline构建项目和更新，具体环境要求如下：<br>1、jenkins pipeline插件安装成功<br>2、要更新的应用已提前部署<br>3、Jenkins slave中需要有kubectl、svn、mvn客户端且环境变量设置准确<br>4、Jenkis slave需要能和master的api-server进行正常通信（这里为了简便，前文构建jenkins server的时候直接对defalut这个service account做rabc授权。其他可选方案是使用~/.kube/config文件实现客户端的授权认证）<br>5、需要配置共享卷(slave编译完成之后把war包copy到共享卷位置)</p>
</blockquote>
<h1 id="一、创建构建任务"><a href="#一、创建构建任务" class="headerlink" title="一、创建构建任务"></a>一、创建构建任务</h1><p>1、点击“jenkins ”——“新建 ”——“pipeline”，输入项目的名称，点击“OK”<br><img src="http://iubest.gitee.io/pic/640-1601168587754.webp" alt="img"></p>
<p>2、下拉到“pipeline”配置，填写脚本<br><img src="http://iubest.gitee.io/pic/640-1601168618729.webp" alt="img"><br>具体脚本内容如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">podTemplate(name: &#39;jenkins-slave&#39;, cloud: &#39;kubernetes&#39;,</span><br><span class="line">  namespace: &#39;default&#39;, label: &#39;jenkins-slave&#39;,</span><br><span class="line">  serviceAccount: &#39;default&#39;, containers: [</span><br><span class="line">  containerTemplate(</span><br><span class="line">      name: &#39;jenkins-slave&#39;,</span><br><span class="line">      image: &#39;harbor.59iedu.com&#x2F;fjhb&#x2F;jenkins-slave-toolkit:2018-08-10-v1&#39;,</span><br><span class="line">      args: &#39;$&#123;computer.jnlpmac&#125; $&#123;computer.name&#125;&#39;,</span><br><span class="line">      ttyEnabled: true,</span><br><span class="line">      privileged: false,</span><br><span class="line">      alwaysPullImage:true,</span><br><span class="line">      )</span><br><span class="line">  ],</span><br><span class="line">  volumes: [</span><br><span class="line">    persistentVolumeClaim(mountPath: &#39;&#x2F;tmp&#x2F;&#39;, claimName: &#39;tomcat-jcsj-data&#39;)</span><br><span class="line">  ]) &#123;</span><br><span class="line">  node(&#39;jenkins-slave&#39;) &#123;</span><br><span class="line">    stage(&#39;svn-checkout&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">          sh &quot;&quot;&quot;</span><br><span class="line">          svn checkout --username&#x3D;yanglw --password&#x3D;mypassword http:&#x2F;&#x2F;192.168.1.81&#x2F;svn&#x2F;fjhbjsb&#x2F;k8s-pipeline-test --non-interactive </span><br><span class="line">          &quot;&quot;&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stage(&#39;mvn-package&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">          sh &quot;&quot;&quot;</span><br><span class="line">          mkdir -p &#x2F;var&#x2F;jenkins_home &amp;&amp; cd k8s-pipeline-test &amp;&amp; mvn clean package &amp;&amp; cp -rpf target&#x2F;*.war &#x2F;tmp&#x2F;</span><br><span class="line">          &quot;&quot;&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stage(&#39;restart&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">          sh &quot;&quot;&quot;</span><br><span class="line">          pod_name&#x3D;&#96;kubectl  get pods -l name&#x3D;jcsj-dev -o name | cut -d&quot;&#x2F;&quot; -f2&#96;</span><br><span class="line">          kubectl delete pod \$pod_name</span><br><span class="line">          &quot;&quot;&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>3、脚本释义<br>podTemplate节指定创建pod的模板和环境</p>
<blockquote>
<p>Name：为pod的名称前缀<br>Cloud：为构建pod的云环境，需要和前面新建的云环境名称一样<br>Namespace：创建pod所在的namespace<br>Label: 创建pod对应的标签<br>serviceAccount：pod使用sa，这里使用default, 所以前文创建jenkins master deployment的时候多创建了一个default-role,这样自动创建出来的的jenkins-slave具有对应的api权限</p>
</blockquote>
<p>containerTemplate节</p>
<blockquote>
<p>指定创建容器的模板，当云环境里面配置pod模板后，容器模板以云环境的配置为准。</p>
</blockquote>
<p>Volumes节</p>
<blockquote>
<p>配置jenkins-slave pod挂载的卷，当云环境里面配置pod模板后，挂载卷以云环境配置为准</p>
</blockquote>
<p>stage节为具体的pipeline步骤</p>
<blockquote>
<p>这里第一步进行svn代码迁出；<br>第二步进行编译，并把包传到共享卷上面；<br>第三步对pod进行删除，因为应用采用deployment方式部署，所以可以实现重新创建pod，达到更新的效果。</p>
</blockquote>
<h1 id="二、构建准备"><a href="#二、构建准备" class="headerlink" title="二、构建准备"></a>二、构建准备</h1><p>在开始构建之前，有必要介绍一下jcsj-dev的环境，这个环境是需要提前部署好的。部署文件如下</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"># cat deploy.yml </span><br><span class="line">---</span><br><span class="line">apiVersion: extensions&#x2F;v1beta1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line">  name: jcsj-dev</span><br><span class="line">  namespace: default</span><br><span class="line">spec:</span><br><span class="line">  replicas: 1</span><br><span class="line">  template:</span><br><span class="line">    metadata:</span><br><span class="line">      labels:</span><br><span class="line">        name: jcsj-dev</span><br><span class="line">    spec:</span><br><span class="line">      containers:</span><br><span class="line">      - name: jcsj-dev</span><br><span class="line">        image: tomcat:latest</span><br><span class="line">        imagePullPolicy: IfNotPresent</span><br><span class="line">        ports:</span><br><span class="line">        - name: web</span><br><span class="line">          containerPort: 8080</span><br><span class="line">        volumeMounts:</span><br><span class="line">        - mountPath: &#x2F;usr&#x2F;local&#x2F;tomcat&#x2F;webapps</span><br><span class="line">          name: tomcat-jcsj-data</span><br><span class="line">      volumes:</span><br><span class="line">      - name: tomcat-jcsj-data</span><br><span class="line">        persistentVolumeClaim:</span><br><span class="line">          claimName: tomcat-jcsj-data</span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Service</span><br><span class="line">metadata:</span><br><span class="line">  name: jcsj-dev</span><br><span class="line">spec:</span><br><span class="line">  type: NodePort</span><br><span class="line">  ports:</span><br><span class="line">    - port: 8080</span><br><span class="line">      targetPort: 8080</span><br><span class="line">      nodePort: 8453</span><br><span class="line">  selector:</span><br><span class="line">name: jcsj-dev</span><br><span class="line"># cat pv.yaml </span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: PersistentVolume</span><br><span class="line">metadata:</span><br><span class="line">  name: tomcat-jcsj-data</span><br><span class="line">spec:</span><br><span class="line">  capacity:</span><br><span class="line">    storage: 5Gi </span><br><span class="line">  accessModes:</span><br><span class="line">  - ReadWriteMany </span><br><span class="line">  nfs: </span><br><span class="line">    path: &#x2F;home&#x2F;tomcat_jcsj</span><br><span class="line">    server: 192.168.115.6</span><br><span class="line">  persistentVolumeReclaimPolicy: Recycle </span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">apiVersion: v1</span><br><span class="line">metadata:</span><br><span class="line">  name: tomcat-jcsj-data</span><br><span class="line">spec:</span><br><span class="line">  accessModes:</span><br><span class="line">    - ReadWriteMany</span><br><span class="line">  resources:</span><br><span class="line">    requests:</span><br><span class="line">      storage: 5Gi</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601168590868.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168587505.webp" alt="img"></p>
<h1 id="三、更新项目代码"><a href="#三、更新项目代码" class="headerlink" title="三、更新项目代码"></a>三、更新项目代码</h1><p>更新index.jsp并提交<br><img src="http://iubest.gitee.io/pic/640-1601168590633.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168590634.webp" alt="img"></p>
<h1 id="四、开始构建"><a href="#四、开始构建" class="headerlink" title="四、开始构建"></a>四、开始构建</h1><p>点击“立即构建”，构建过程中可以通过控制台输出看具体的日志信息<br><img src="http://iubest.gitee.io/pic/640-1601168587499.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168590869.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168587775.webp" alt="img"></p>
<p>构建过程中观察jenkins master的日志输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"># kubectl logs -f jenkins-master-588b89c75f-ztvgm </span><br><span class="line">INFO: Started provisioning Kubernetes Pod Template from kubernetes with 1 executors. Remaining excess workload: 0</span><br><span class="line">Aug 13, 2018 10:14:22 AM hudson.slaves.NodeProvisioner$2 run</span><br><span class="line">INFO: Kubernetes Pod Template provisioning successfully completed. We have now 2 computer(s)</span><br><span class="line">Aug 13, 2018 10:14:22 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Created Pod: jenkins-slave-gjjbh in namespace default</span><br><span class="line">Aug 13, 2018 10:14:22 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for Pod to be scheduled (0&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:28 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Container is waiting jenkins-slave-gjjbh [jnlp]: ContainerStateWaiting(message&#x3D;null, reason&#x3D;ContainerCreating, additionalProperties&#x3D;&#123;&#125;)</span><br><span class="line">Aug 13, 2018 10:14:28 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for Pod to be scheduled (1&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:34 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (1&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:35 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (2&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:36 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (3&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:37 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (4&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:38 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (5&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:39 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (6&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:40 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (7&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:41 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (8&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:42 AM hudson.TcpSlaveAgentListener$ConnectionHandler run</span><br><span class="line">INFO: Accepted JNLP4-connect connection #1 from &#x2F;172.30.66.4:47408</span><br><span class="line">Aug 13, 2018 10:14:42 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (9&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:43 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (10&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:44 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (11&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:45 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (12&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:46 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (13&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:47 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (14&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:48 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (15&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:49 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (16&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:50 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (17&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:51 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (18&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:52 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (19&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:14:53 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesLauncher launch</span><br><span class="line">INFO: Waiting for agent to connect (20&#x2F;100): jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:15:12 AM org.csanchez.jenkins.plugins.kubernetes.pipeline.ContainerExecDecorator$1 doLaunch</span><br><span class="line">INFO: Created process inside pod: [jenkins-slave-gjjbh], container: [jnlp] with pid:[-1]</span><br><span class="line">Aug 13, 2018 10:15:21 AM org.csanchez.jenkins.plugins.kubernetes.pipeline.ContainerExecDecorator$1 doLaunch</span><br><span class="line">INFO: Created process inside pod: [jenkins-slave-gjjbh], container: [jnlp] with pid:[-1]</span><br><span class="line">Aug 13, 2018 10:16:04 AM org.csanchez.jenkins.plugins.kubernetes.pipeline.ContainerExecDecorator$1 doLaunch</span><br><span class="line">INFO: Created process inside pod: [jenkins-slave-gjjbh], container: [jnlp] with pid:[-1]</span><br><span class="line">Aug 13, 2018 10:16:06 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave _terminate</span><br><span class="line">INFO: Terminating Kubernetes instance for agent jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:16:06 AM jenkins.slaves.DefaultJnlpSlaveReceiver channelClosed</span><br><span class="line">WARNING: Computer.threadPoolForRemoting [#8] for jenkins-slave-gjjbh terminated</span><br><span class="line">java.nio.channels.ClosedChannelException</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.ChannelApplicationLayer.onReadClosed(ChannelApplicationLayer.java:208)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.ApplicationLayer.onRecvClosed(ApplicationLayer.java:222)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.ProtocolStack$Ptr.onRecvClosed(ProtocolStack.java:832)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.FilterLayer.onRecvClosed(FilterLayer.java:287)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.onRecvClosed(SSLEngineFilterLayer.java:181)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.switchToNoSecure(SSLEngineFilterLayer.java:283)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.processWrite(SSLEngineFilterLayer.java:503)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.processQueuedWrites(SSLEngineFilterLayer.java:248)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.doSend(SSLEngineFilterLayer.java:200)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.SSLEngineFilterLayer.doCloseSend(SSLEngineFilterLayer.java:213)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.ProtocolStack$Ptr.doCloseSend(ProtocolStack.java:800)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.ApplicationLayer.doCloseWrite(ApplicationLayer.java:173)</span><br><span class="line">        at org.jenkinsci.remoting.protocol.impl.ChannelApplicationLayer$ByteBufferCommandTransport.closeWrite(ChannelApplicationLayer.java:311)</span><br><span class="line">        at hudson.remoting.Channel.close(Channel.java:1295)</span><br><span class="line">        at hudson.remoting.Channel.close(Channel.java:1263)</span><br><span class="line">        at hudson.slaves.SlaveComputer.closeChannel(SlaveComputer.java:708)</span><br><span class="line">        at hudson.slaves.SlaveComputer.access$800(SlaveComputer.java:96)</span><br><span class="line">        at hudson.slaves.SlaveComputer$3.run(SlaveComputer.java:626)</span><br><span class="line">        at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)</span><br><span class="line">        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)</span><br><span class="line">        at java.util.concurrent.FutureTask.run(FutureTask.java:266)</span><br><span class="line">        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)</span><br><span class="line">        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)</span><br><span class="line">        at java.lang.Thread.run(Thread.java:748)</span><br><span class="line"></span><br><span class="line">Aug 13, 2018 10:16:06 AM org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateStepExecution$PodTemplateCallback finished</span><br><span class="line">INFO: Removing pod template and deleting pod jenkins-slave-7gnwx from cloud kubernetes</span><br><span class="line">Aug 13, 2018 10:16:06 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave _terminate</span><br><span class="line">INFO: Terminated Kubernetes instance for agent default&#x2F;jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:16:06 AM org.csanchez.jenkins.plugins.kubernetes.KubernetesSlave _terminate</span><br><span class="line">INFO: Disconnected computer jenkins-slave-gjjbh</span><br><span class="line">Terminated Kubernetes instance for agent default&#x2F;jenkins-slave-gjjbh</span><br><span class="line">Aug 13, 2018 10:16:06 AM org.csanchez.jenkins.plugins.kubernetes.pipeline.PodTemplateStepExecution$PodTemplateCallback finished</span><br><span class="line">WARNING: Failed to delete pod for agent default&#x2F;jenkins-slave-7gnwx: not found</span><br><span class="line">Aug 13, 2018 10:16:07 AM org.jenkinsci.plugins.workflow.job.WorkflowRun finish</span><br><span class="line">INFO: k8s-pipeline-test #1 completed: SUCCESS</span><br></pre></td></tr></table></figure>

<h1 id="五、验证结果"><a href="#五、验证结果" class="headerlink" title="五、验证结果"></a>五、验证结果</h1><p><img src="http://iubest.gitee.io/pic/640-1601168590633.webp" alt="img"></p>
<h1 id="六、后记"><a href="#六、后记" class="headerlink" title="六、后记"></a>六、后记</h1><p>本文仅介绍的pipeline方式构建项目的过程，实际应用中还需要解决若干问题，例如：</p>
<blockquote>
<p>1、svn checkout步骤需要明文配置用户名和密码，存在安全性问题<br>2、 每次执行构建都需要全量迁出代码，效率不高<br>3、要更新的应用需要提前部署好，不能适配首次编译部署场景<br>4、对共享卷存在依赖，解耦问题需要解决</p>
</blockquote>
<h1 id="七、续"><a href="#七、续" class="headerlink" title="七、续"></a>七、续</h1><p>研究了pipeline的语法结合shell脚本，基本可以解决上述问题，以下是pipeline代码</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">podTemplate(name: &#39;jenkins-slave&#39;, cloud: &#39;kubernetes&#39;,</span><br><span class="line">  namespace: &#39;default&#39;, label: &#39;jenkins-slave&#39;) </span><br><span class="line">  &#123;</span><br><span class="line">  node(&#39;jenkins-slave&#39;) &#123;</span><br><span class="line">    stage(&#39;svn-checkout&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">         checkout([$class: &#39;SubversionSCM&#39;, </span><br><span class="line">         additionalCredentials: [], </span><br><span class="line">         excludedCommitMessages: &#39;&#39;, </span><br><span class="line">         excludedRegions: &#39;&#39;, </span><br><span class="line">         excludedRevprop: &#39;&#39;, </span><br><span class="line">         excludedUsers: &#39;&#39;, </span><br><span class="line">         filterChangelog: false, </span><br><span class="line">         ignoreDirPropChanges: false, </span><br><span class="line">         includedRegions: &#39;&#39;, </span><br><span class="line">         locations: [[cancelProcessOnExternalsFail: true, </span><br><span class="line">         credentialsId: &#39;ef21f2d3-cb39-468a-aa19-9175d96a365f&#39;, </span><br><span class="line">         depthOption: &#39;infinity&#39;, </span><br><span class="line">         ignoreExternalsOption: true, </span><br><span class="line">         local: &#39;.&#39;, </span><br><span class="line">         remote: &#39;http:&#x2F;&#x2F;192.168.1.81&#x2F;svn&#x2F;fjhbjsb&#x2F;k8s-pipeline-test&#39;]], </span><br><span class="line">         quietOperation: true, workspaceUpdater: [$class: &#39;UpdateUpdater&#39;]])</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stage(&#39;mvn-package&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">          sh &quot;&quot;&quot;</span><br><span class="line">          mvn clean package </span><br><span class="line">          &quot;&quot;&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stage(&#39;build-docker-image&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">        sh &#39;&#39;&#39;</span><br><span class="line">        IMAGE_NAME&#x3D;&quot;harbor.59iedu.com&#x2F;fjhb&#x2F;jcsj:v$BUILD_NUMBER&quot;   </span><br><span class="line">        CONATINER_NAME&#x3D;&quot;jcsj-dev&quot; </span><br><span class="line">        echo &quot;192.168.1.227 harbor.59iedu.com&quot; &gt;&gt; &#x2F;etc&#x2F;hosts </span><br><span class="line">        sed -i  &quot;s&#x2F;BUILD_NUMBER&#x2F;$BUILD_NUMBER&#x2F;g&quot; tomcat.yaml</span><br><span class="line">        sed -i  &quot;s&#x2F;BUILD_NUMBER&#x3D;1&#x2F;BUILD_NUMBER&#x3D;$BUILD_NUMBER&#x2F;g&quot; rolling-update.sh </span><br><span class="line">        docker login harbor.59iedu.com -u admin -p Harbor12345 </span><br><span class="line">        docker build -t $IMAGE_NAME . </span><br><span class="line">        docker push $IMAGE_NAME</span><br><span class="line">        &#39;&#39;&#39;</span><br><span class="line"></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stage(&#39;apply update&#39;) &#123;</span><br><span class="line">      container(&#39;jnlp&#39;) &#123;</span><br><span class="line">          sh &#39;&#39;&#39;</span><br><span class="line">          NAMESPACE&#x3D;default</span><br><span class="line">          DEPLOYMENT&#x3D;tomcat-jcsj</span><br><span class="line">          REGISTRY&#x3D;harbor.59iedu.com&#x2F;fjhb&#x2F;jcsj:v$BUILD_NUMBER</span><br><span class="line">          FILE&#x3D;tomcat.yaml       </span><br><span class="line">          STATUS&#x3D;$(kubectl get deployment -l k8s-app&#x3D;$DEPLOYMENT -o name -n $NAMESPACE)</span><br><span class="line">          if [ ! $STATUS ];then</span><br><span class="line">          kubectl create -f $FILE</span><br><span class="line">          else</span><br><span class="line">          kubectl set image deployment&#x2F;$DEPLOYMENT $DEPLOYMENT&#x3D;$REGISTRY --namespace&#x3D;$NAMESPACE</span><br><span class="line">          fi    </span><br><span class="line">          &#39;&#39;&#39;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中，svn-checkout节代码通过“流水线语法”工具生成，同时需要给default sa用户相应的权限,否则无法实现apply update节配置</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># kubectl create clusterrolebinding k8s-admin --clusterrole&#x3D;cluster-admin  --user&#x3D;system:serviceaccount:default:default</span><br></pre></td></tr></table></figure>

<p><img src="http://iubest.gitee.io/pic/640-1601168587776.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168587774.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168590779.webp" alt="img"><br><img src="http://iubest.gitee.io/pic/640-1601168587774.webp" alt="img"></p>
<p>文章转载地址如下:</p>
<ul>
<li><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https:&#x2F;&#x2F;blog.51cto.com&#x2F;ylw6006&#x2F;2159861</span><br></pre></td></tr></table></figure></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-k8s/K8S使用ceph-csi持久化存储之CephFS"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/K8S%E4%BD%BF%E7%94%A8ceph-csi%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8%E4%B9%8BCephFS/"
    >K8S使用ceph-csi持久化存储之CephFS.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/K8S%E4%BD%BF%E7%94%A8ceph-csi%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8%E4%B9%8BCephFS/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="K8S使用ceph-csi持久化存储之CephFS"><a href="#K8S使用ceph-csi持久化存储之CephFS" class="headerlink" title="K8S使用ceph-csi持久化存储之CephFS"></a>K8S使用ceph-csi持久化存储之CephFS</h1><p>继上一篇介绍Ceph-CSI的RBD对接K8S的文章（<a target="_blank" rel="noopener" href="http://mp.weixin.qq.com/s?__biz=MzI5ODQ2MzI3NQ==&mid=2247493191&idx=2&sn=f11084b671288655ec87154deef177b2&chksm=eca7df03dbd0561583efeaef2b9df1e11012f29cd7de681c507b4df029e03943ce97f95810d9&scene=21#wechat_redirect">K8S使用ceph-csi持久化存储之RBD</a>）后，本篇章介绍了Ceph-CSI的CephFS对接K8S的使用。请食用！*</p>
<h3 id="一、集群和组件版本"><a href="#一、集群和组件版本" class="headerlink" title="一、集群和组件版本"></a>一、集群和组件版本</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">K8S集群：1.17.3+</span><br><span class="line">Ceph集群：Octopus（stables）</span><br><span class="line">Ceph-CSI：release-v3.1</span><br><span class="line">snapshotter-controller：release-2.1</span><br><span class="line">Linue kernel：3.10.0-1127.19.1.el7.x86_64 +</span><br><span class="line">注意：1）CephFS的快照功能需要Ceph版本为O版；2）创建了快照的pvc挂载需要内核4.14版本。</span><br></pre></td></tr></table></figure>

<ul>
<li>镜像版本：</li>
</ul>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">docker pull quay.io/k8scsi/csi-snapshotter:v2.1.1</span><br><span class="line">docker pull quay.io/k8scsi/csi-snapshotter:v2.1.0</span><br><span class="line">docker pull quay.io/k8scsi/csi-resizer:v0.5.0</span><br><span class="line">docker pull quay.io/k8scsi/csi-provisioner:v1.6.0</span><br><span class="line">docker pull quay.io/k8scsi/csi-node-driver-registrar:v1.3.0</span><br><span class="line">docker pull quay.io/k8scsi/csi-attacher:v2.1.1</span><br><span class="line">docker pull quay.io/cephcsi/cephcsi:v3.1-canary</span><br><span class="line">docker pull quay.io/k8scsi/snapshot-controller:v2.0.1</span><br></pre></td></tr></table></figure>

<h3 id="二、部署"><a href="#二、部署" class="headerlink" title="二、部署"></a>二、部署</h3><h4 id="1）部署Ceph-CSI"><a href="#1）部署Ceph-CSI" class="headerlink" title="1）部署Ceph-CSI"></a>1）部署Ceph-CSI</h4><h5 id="1-1）克隆代码"><a href="#1-1）克隆代码" class="headerlink" title="1.1）克隆代码"></a>1.1）克隆代码</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> git <span class="built_in">clone</span> https://github.com/ceph/ceph-csi.git</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="built_in">cd</span> ceph-csi/deploy/cephfs/kubernetes</span></span><br></pre></td></tr></table></figure>

<h5 id="1-2）修改yaml文件"><a href="#1-2）修改yaml文件" class="headerlink" title="1.2）修改yaml文件"></a>1.2）修改yaml文件</h5><p><em>1.2.1）配置csi-config-map.yaml文件链接ceph集群的信息</em></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> cat csi-config-map.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: ConfigMap</span><br><span class="line">data:</span><br><span class="line">  config.json: |-</span><br><span class="line">    [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;clusterID&quot;: &quot;c7b4xxf7-c61e-4668-9xx0-82c9xx5e3696&quot;,    // 通过ceph集群的ID</span><br><span class="line">        &quot;monitors&quot;: [</span><br><span class="line">          &quot;xxx.xxx.xxx.xxx:6789&quot;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">metadata:</span><br><span class="line">  name: ceph-csi-config</span><br></pre></td></tr></table></figure>

<p><em>1.2.2）部署cephfs相关的CSI</em></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f ceph-csi/deploy/cephfs/kubernetes/</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pods | grep cephfs</span></span><br><span class="line">csi-cephfsplugin-8mvmf                          3/3     Running   0          2d21h</span><br><span class="line">csi-cephfsplugin-provisioner-78f64778db-42xwc   6/6     Running   0          2d21h</span><br><span class="line">csi-cephfsplugin-provisioner-78f64778db-jzpvd   6/6     Running   0          2d21h</span><br><span class="line">csi-cephfsplugin-provisioner-78f64778db-ntcmd   6/6     Running   0          2d21h</span><br><span class="line">csi-cephfsplugin-zlqzg                          3/3     Running   0          2d21h</span><br></pre></td></tr></table></figure>

<h4 id="2-快照功能需要安装快照控制器支持："><a href="#2-快照功能需要安装快照控制器支持：" class="headerlink" title="2)快照功能需要安装快照控制器支持："></a>2)快照功能需要安装快照控制器支持：</h4><p><em>由于上篇 “K8S使用ceph-csi持久化存储之RBD” 以及部署，这里不需要重复部署。至此，Ceph-CSI和snapshot-controller安装完成。下面进行功能测试。测试功能前需要在ceph集群中创建对应的存储池：</em></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">// 查看集群状态</span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph -s</span></span><br><span class="line">  cluster:</span><br><span class="line">    id:     c7b43ef7-c61e-4668-9970-82c9775e3696</span><br><span class="line">    health: HEALTH_OK</span><br><span class="line"> </span><br><span class="line">  services:</span><br><span class="line">    mon: 1 daemons, quorum cka-node-01 (age 24h)</span><br><span class="line">    mgr: cka-node-01(active, since 24h), standbys: cka-node-02, cka-node-03</span><br><span class="line">    mds: cephfs:1 &#123;0=cka-node-01=up:active&#125; 2 up:standby</span><br><span class="line">    osd: 3 osds: 3 up, 3 in</span><br><span class="line">    rgw: 1 daemon active (cka-node-01)</span><br><span class="line"> </span><br><span class="line">  task status:</span><br><span class="line">    scrub status:</span><br><span class="line">        mds.cka-node-01: idle</span><br><span class="line"> </span><br><span class="line">  data:</span><br><span class="line">    pools:   7 pools, 184 pgs</span><br><span class="line">    objects: 827 objects, 1.7 GiB</span><br><span class="line">    usage:   8.1 GiB used, 52 GiB / 60 GiB avail</span><br><span class="line">    pgs:     184 active+clean</span><br><span class="line"> </span><br><span class="line">  io:</span><br><span class="line">    client:   32 KiB/s rd, 0 B/s wr, 31 op/s rd, 21 op/s wr</span><br><span class="line"> </span><br><span class="line">// 创建cephfs存储池fs_metadata，fs_data</span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph osd pool create fs_metadata 8 8</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph osd pool create fs_data 8 8</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph fs new cephfs fs_metadata fs_data</span></span><br><span class="line"></span><br><span class="line"> </span><br><span class="line">// 获取集群信息和查看用户key</span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph mon dump</span></span><br><span class="line">dumped monmap epoch 3</span><br><span class="line">epoch 3</span><br><span class="line">fsid c7b43ef7-c61e-4668-9970-82c9775e3696</span><br><span class="line">last_changed 2020-09-11 11:05:25.529648</span><br><span class="line">created 2020-09-10 16:22:52.967856</span><br><span class="line">min_mon_release 14 (nautilus)</span><br><span class="line">0: [v2:10.0.xxx.xxx0:3300/0,v1:10.0.xxx.xxx:6789/0] mon.cka-node-01</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> ceph auth get client.admin</span></span><br><span class="line">exported keyring for client.admin</span><br><span class="line">[client.admin]</span><br><span class="line">	key = AQBg4llf+9CAGxdsx4tQzS+0OssssiTEQ==</span><br><span class="line">	caps mds = &quot;allow *&quot;</span><br><span class="line">	caps mgr = &quot;allow *&quot;</span><br><span class="line">	caps mon = &quot;allow *&quot;</span><br><span class="line">	caps osd = &quot;allow *&quot;</span><br></pre></td></tr></table></figure>

<h3 id="三、验证"><a href="#三、验证" class="headerlink" title="三、验证"></a>三、验证</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">验证如下功能：</span><br><span class="line">1）创建cephfs类型pvc给pod使用；</span><br><span class="line">2）创建cephfs类型pvc的快照，并验证基于快照恢复的可用性（CephFS的快照功能需要Ceph版本为O版）；</span><br><span class="line">3）创建快照后的pvc重复创建挂载（创建了快照的pvc挂载需要内核4.14版本）；</span><br></pre></td></tr></table></figure>

<h5 id="1、创建cephfs类型pvc给pod使用："><a href="#1、创建cephfs类型pvc给pod使用：" class="headerlink" title="1、创建cephfs类型pvc给pod使用："></a>1、创建cephfs类型pvc给pod使用：</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br></pre></td><td class="code"><pre><span class="line">1.1) 创建连接ceph集群的秘钥</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat secret.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Secret</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-cephfs-secret</span><br><span class="line">  namespace: default</span><br><span class="line">stringData:</span><br><span class="line"><span class="meta">  #</span><span class="bash"> Required <span class="keyword">for</span> statically provisioned volumes</span></span><br><span class="line">  userID: admin</span><br><span class="line">  userKey: AQBg4llf+9CAGdsAds4tQzS+0O7dscB5ZTiTEQ==</span><br><span class="line"></span><br><span class="line"><span class="meta">  #</span><span class="bash"> Required <span class="keyword">for</span> dynamically provisioned volumes</span></span><br><span class="line">  adminID: admin</span><br><span class="line">  adminKey: AQBg4llf+9CAGdsAds4tQzS+0O7dscB5ZTiTEQ==</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f secret.yaml</span></span><br><span class="line">1.2) 创建storeclass</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat storageclass.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: storage.k8s.io/v1</span><br><span class="line">kind: StorageClass</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-cephfs-sc</span><br><span class="line">provisioner: cephfs.csi.ceph.com</span><br><span class="line">parameters:</span><br><span class="line">  clusterID: c7b43ef7-c61e-4668-9970-82c9775e3696</span><br><span class="line">  fsName: cephfs</span><br><span class="line">  pool: fs_data</span><br><span class="line">  rootPath: /test</span><br><span class="line">  csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret</span><br><span class="line">  csi.storage.k8s.io/provisioner-secret-namespace: default</span><br><span class="line">  csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret</span><br><span class="line">  csi.storage.k8s.io/controller-expand-secret-namespace: default</span><br><span class="line">  csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret</span><br><span class="line">  csi.storage.k8s.io/node-stage-secret-namespace: default</span><br><span class="line">reclaimPolicy: Delete</span><br><span class="line">allowVolumeExpansion: true</span><br><span class="line">mountOptions:</span><br><span class="line">  - discard</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f storageclass.yaml</span></span><br><span class="line">1.3)基于storeclass创建pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pvc.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-cephfs-pvc</span><br><span class="line">spec:</span><br><span class="line">  accessModes:</span><br><span class="line">    - ReadWriteMany</span><br><span class="line">  resources:</span><br><span class="line">    requests:</span><br><span class="line">      storage: 1Gi</span><br><span class="line">  storageClassName: csi-cephfs-sc</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pvc.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pvc csi-cephfs-pvc</span></span><br><span class="line">NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE</span><br><span class="line">csi-cephfs-pvc   Bound    pvc-72b5c1e0-b2d5-4f93-8779-96cb03efa733   1Gi        RWX            csi-cephfs-sc   2d21h</span><br><span class="line">1.4）创建pod应用pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pod.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-cephfs-demo-pod</span><br><span class="line">spec:</span><br><span class="line">  containers:</span><br><span class="line">    - name: web-server</span><br><span class="line">      image: nginx</span><br><span class="line">      volumeMounts:</span><br><span class="line">        - name: mypvc</span><br><span class="line">          mountPath: /var/lib/www</span><br><span class="line">  volumes:</span><br><span class="line">    - name: mypvc</span><br><span class="line">      persistentVolumeClaim:</span><br><span class="line">        claimName: csi-cephfs-pvc</span><br><span class="line">        readOnly: false</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pod.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pods csi-cephfs-demo-pod</span></span><br><span class="line">NAME                  READY   STATUS    RESTARTS   AGE</span><br><span class="line">csi-cephfs-demo-pod   1/1     Running   0          2d21h</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl <span class="built_in">exec</span> -ti csi-cephfs-demo-pod -- bash</span></span><br><span class="line">root@csi-cephfs-demo-pod:/# df -h</span><br><span class="line">Filesystem               Size  Used Avail Use% Mounted on</span><br><span class="line">overlay                  199G  7.9G  191G   4% /</span><br><span class="line">tmpfs                     64M     0   64M   0% /dev</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /sys/fs/cgroup</span><br><span class="line">/dev/mapper/centos-root  199G  7.9G  191G   4% /etc/hosts</span><br><span class="line">shm                       64M     0   64M   0% /dev/shm</span><br><span class="line">ceph-fuse                1.0G     0  1.0G   0% /var/lib/www</span><br><span class="line">tmpfs                    7.9G   12K  7.9G   1% /run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /proc/acpi</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /proc/scsi</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /sys/firmware</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> 写入文件，用于后续快照验证</span></span><br><span class="line">root@csi-cephfs-demo-pod:/# cd /var/lib/www;echo &quot;abce&quot; &gt; test</span><br><span class="line">root@csi-cephfs-demo-pod:/var/lib/www# cat test</span><br><span class="line">abce</span><br></pre></td></tr></table></figure>

<h5 id="2）创建cephfs类型pvc的快照，并验证基于快照恢复的可用性（CephFS的快照功能需要Ceph版本为O版）："><a href="#2）创建cephfs类型pvc的快照，并验证基于快照恢复的可用性（CephFS的快照功能需要Ceph版本为O版）：" class="headerlink" title="2）创建cephfs类型pvc的快照，并验证基于快照恢复的可用性（CephFS的快照功能需要Ceph版本为O版）："></a>2）创建cephfs类型pvc的快照，并验证基于快照恢复的可用性（CephFS的快照功能需要Ceph版本为O版）：</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line">2.1)创建上一步pvc的快照</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat snapshot.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: snapshot.storage.k8s.io/v1beta1</span><br><span class="line">kind: VolumeSnapshot</span><br><span class="line">metadata:</span><br><span class="line">  name: cephfs-pvc-snapshot</span><br><span class="line">spec:</span><br><span class="line">  volumeSnapshotClassName: csi-cephfsplugin-snapclass</span><br><span class="line">  source:</span><br><span class="line">    persistentVolumeClaimName: csi-cephfs-pvc</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f snapshot.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get VolumeSnapshot cephfs-pvc-snapshot</span></span><br><span class="line">NAME                  READYTOUSE   SOURCEPVC        SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS                SNAPSHOTCONTENT                                    CREATIONTIME   AGE</span><br><span class="line">cephfs-pvc-snapshot   true         csi-cephfs-pvc                           1Gi           csi-cephfsplugin-snapclass   snapcontent-6100977b-96e0-43ae-903a-e8136b0f6ced   2d13h          2d21h</span><br><span class="line">2.2)创建基于快照恢复的pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pvc-restore.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">metadata:</span><br><span class="line">  name: cephfs-pvc-restore</span><br><span class="line">spec:</span><br><span class="line">  storageClassName: csi-cephfs-sc</span><br><span class="line">  dataSource:</span><br><span class="line">    name: cephfs-pvc-snapshot</span><br><span class="line">    kind: VolumeSnapshot</span><br><span class="line">    apiGroup: snapshot.storage.k8s.io</span><br><span class="line">  accessModes:</span><br><span class="line">    - ReadWriteMany</span><br><span class="line">  resources:</span><br><span class="line">    requests:</span><br><span class="line">      storage: 1Gi</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pvc-restore.yaml</span></span><br><span class="line">2.3)创建pod应用快照恢复的pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pod-restore.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-cephfs-restore-demo-pod</span><br><span class="line">spec:</span><br><span class="line">  containers:</span><br><span class="line">    - name: web-server</span><br><span class="line">      image: nginx</span><br><span class="line">      volumeMounts:</span><br><span class="line">        - name: mypvc</span><br><span class="line">          mountPath: /var/lib/www/html</span><br><span class="line">  volumes:</span><br><span class="line">    - name: mypvc</span><br><span class="line">      persistentVolumeClaim:</span><br><span class="line">        claimName: cephfs-pvc-restore</span><br><span class="line">        readOnly: false</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pod-restore.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pods csi-cephfs-restore-demo-pod</span></span><br><span class="line">NAME                          READY   STATUS    RESTARTS   AGE</span><br><span class="line">csi-cephfs-restore-demo-pod   1/1     Running   0          2d21h</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl <span class="built_in">exec</span> -ti csi-cephfs-restore-demo-pod -- bash</span></span><br><span class="line">root@csi-cephfs-restore-demo-pod:/# df -h</span><br><span class="line">Filesystem               Size  Used Avail Use% Mounted on</span><br><span class="line">overlay                  199G  7.9G  191G   4% /</span><br><span class="line">tmpfs                     64M     0   64M   0% /dev</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /sys/fs/cgroup</span><br><span class="line">/dev/mapper/centos-root  199G  7.9G  191G   4% /etc/hosts</span><br><span class="line">shm                       64M     0   64M   0% /dev/shm</span><br><span class="line">ceph-fuse                1.0G     0  1.0G   0% /var/lib/www/html</span><br><span class="line">tmpfs                    7.9G   12K  7.9G   1% /run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /proc/acpi</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /proc/scsi</span><br><span class="line">tmpfs                    7.9G     0  7.9G   0% /sys/firmware</span><br><span class="line"></span><br><span class="line">root@csi-cephfs-restore-demo-pod:/# cd /var/lib/www/html</span><br><span class="line">root@csi-cephfs-restore-demo-pod:/var/lib/www/html# cat test</span><br><span class="line">abce</span><br><span class="line"> </span><br><span class="line">//基于快照恢复数据功能正常</span><br></pre></td></tr></table></figure>

<h5 id="3）创建快照后的pvc重复创建挂载（创建了快照的pvc挂载需要内核4-14版本，RBD类型也需要升级）："><a href="#3）创建快照后的pvc重复创建挂载（创建了快照的pvc挂载需要内核4-14版本，RBD类型也需要升级）：" class="headerlink" title="3）创建快照后的pvc重复创建挂载（创建了快照的pvc挂载需要内核4.14版本，RBD类型也需要升级）："></a>3）创建快照后的pvc重复创建挂载（创建了快照的pvc挂载需要内核4.14版本，RBD类型也需要升级）：</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">3.1)删除csi-cephfs-demo-pod这个pod后重建</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl delete -f pod.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pod.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pods csi-cephfs-demo-pod</span></span><br><span class="line">NAME                  READY   STATUS    RESTARTS   AGE</span><br><span class="line">csi-cephfs-demo-pod   0/1     ContainerCreating   0          2d22h</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl describe pods csi-cephfs-demo-pod</span></span><br><span class="line">...</span><br><span class="line">Events:</span><br><span class="line">  Type     Reason                  Age              From                     Message</span><br><span class="line">  ----     ------                  ----             ----                     -------</span><br><span class="line">  Normal   Scheduled               &lt;unknown&gt;        default-scheduler        Successfully assigned jonathan/csi-rbd-demo-pod to 10.21.xxx.xxx</span><br><span class="line">  Normal   SuccessfulAttachVolume  25s              attachdetach-controller  AttachVolume.Attach succeeded for volume &quot;pvc-d6ed5203-36c7-454b-8a8e-b49a084a0531&quot;</span><br><span class="line">  Warning  FailedMount             0s (x5 over 9s)  kubelet, 10.21.xxx.xxx    MountVolume.MountDevice failed for volume &quot;pvc-d6ed5203-36c7-454b-8a8e-b49a084a0531&quot; : rpc error: code = Internal desc = rbd: map failed with error an error (exit status 6) occurred while running rbd args: [--id csi -m 10.xxx.xxx.xxx --keyfile=***stripped*** map volumes/csi-vol-9f2dcd03-f97e-11ea-8104-a608005a6960 --device-type krbd], rbd error output: rbd: sysfs write failed</span><br><span class="line">rbd: map failed: (6) No such device or address</span><br><span class="line">3.2）经过分析，该报错是因为node节点的Linux内核版本不支持，需要升级内核。此时的内核版本是3.10版本，升级到4.14版本后问题解决。如下：</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get node -o wide</span></span><br><span class="line">NAME          STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION            CONTAINER-RUNTIME</span><br><span class="line">cka-node-01   Ready    master   60d   v1.17.3   10.0.xxx.xxx   &lt;none&gt;        CentOS Linux 7 (Core)   4.14.119-200.el7.x86_64   docker://19.3.12</span><br><span class="line">cka-node-02   Ready    &lt;none&gt;   60d   v1.17.3   10.0.xxx.xxx   &lt;none&gt;        CentOS Linux 7 (Core)   4.14.119-200.el7.x86_64   docker://19.3.12</span><br><span class="line">cka-node-03   Ready    &lt;none&gt;   60d   v1.17.3   10.0.xxx.xxx   &lt;none&gt;        CentOS Linux 7 (Core)   4.14.119-200.el7.x86_64   docker://19.3.12</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pods csi-cephfs-demo-pod</span></span><br><span class="line">NAME                  READY   STATUS    RESTARTS   AGE</span><br><span class="line">csi-cephfs-demo-pod   1/1     Running   0          2d01h</span><br><span class="line">注意：目前，每个CephFS文件系统限制为400个快照，如果PVC有快照，也无法删除，删除PVC之前，请确保删除PVC上的所有快照。</span><br></pre></td></tr></table></figure>

<h5 id="其他参考"><a href="#其他参考" class="headerlink" title="其他参考"></a>其他参考</h5><h5 id="3）扩容pvc大小："><a href="#3）扩容pvc大小：" class="headerlink" title="3）扩容pvc大小："></a>3）扩容pvc大小：</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">3.1)修改rbd-pvc的容量大小</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pvc.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">metadata:</span><br><span class="line">  name: rbd-pvc</span><br><span class="line">spec:</span><br><span class="line">  accessModes:</span><br><span class="line">    - ReadWriteOnce</span><br><span class="line">  resources:</span><br><span class="line">    requests:</span><br><span class="line">      storage: 100Gi    // 由1G改为100G</span><br><span class="line">  storageClassName: csi-rbd-sc</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pvc.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get pvc rbd-pvc</span></span><br><span class="line">NAME      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE</span><br><span class="line">rbd-pvc   Bound    pvc-11b931b0-7cb5-40e1-815b-c15659310593   100Gi      RWO            csi-rbd-sc     40h</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl <span class="built_in">exec</span> -ti csi-rbd-demo-pod -- bash</span></span><br><span class="line">root@csi-rbd-demo-pod:/# df -h</span><br><span class="line">Filesystem               Size  Used Avail Use% Mounted on</span><br><span class="line">overlay                  199G  7.4G  192G   4% /</span><br><span class="line">tmpfs                     64M     0   64M   0% /dev</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/fs/cgroup</span><br><span class="line">/dev/mapper/centos-root  199G  7.4G  192G   4% /etc/hosts</span><br><span class="line">shm                       64M     0   64M   0% /dev/shm</span><br><span class="line">/dev/rbd0                 99G  6.8M   99G   1% /var/lib/www/html    // 扩容正常</span><br><span class="line">tmpfs                    7.8G   12K  7.8G   1% /run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/acpi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/scsi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/firmware</span><br><span class="line"> </span><br><span class="line">// 再次写入数据用于后续第二次创建快照</span><br><span class="line">root@csi-rbd-demo-pod:# cd /var/lib/www/html;mkdir test;echo &quot;abc&quot; &gt; test/demo.txt;echo &quot;abc&quot; &gt;&gt; /var/lib/www/html/demo/test.txt</span><br><span class="line">root@csi-rbd-demo-pod:/var/lib/www/html# cat test/demo.txt</span><br><span class="line">abc</span><br><span class="line">root@csi-rbd-demo-pod:/var/lib/www/html# cat demo/test.txt</span><br><span class="line">snapshot test</span><br><span class="line">abc</span><br></pre></td></tr></table></figure>

<h5 id="4）同一个pvc重复创建快照："><a href="#4）同一个pvc重复创建快照：" class="headerlink" title="4）同一个pvc重复创建快照："></a>4）同一个pvc重复创建快照：</h5><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line">4.1)再次对rbd-pvc创建快照</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat snapshot-1.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: snapshot.storage.k8s.io/v1beta1</span><br><span class="line">kind: VolumeSnapshot</span><br><span class="line">metadata:</span><br><span class="line">  name: rbd-pvc-snapshot-1</span><br><span class="line">spec:</span><br><span class="line">  volumeSnapshotClassName: csi-rbdplugin-snapclass</span><br><span class="line">  source:</span><br><span class="line">    persistentVolumeClaimName: rbd-pvc</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f snapshot-1.yaml</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl get VolumeSnapshot rbd-pvc-snapshot-1</span></span><br><span class="line">NAME                 READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS             SNAPSHOTCONTENT                                    CREATIONTIME   AGE</span><br><span class="line">rbd-pvc-snapshot-1   true         rbd-pvc                             100Gi         csi-rbdplugin-snapclass   snapcontent-b82dceb0-7ba6-4a3e-88ab-2220b729d85f   18h            18h</span><br><span class="line">4.2)基于rbd-pvc-snapshot-1快照恢复pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pvc-restore-1.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: PersistentVolumeClaim</span><br><span class="line">metadata:</span><br><span class="line">  name: rbd-pvc-restore-1</span><br><span class="line">spec:</span><br><span class="line">  storageClassName: csi-rbd-sc</span><br><span class="line">  dataSource:</span><br><span class="line">    name: rbd-pvc-snapshot-1</span><br><span class="line">    kind: VolumeSnapshot</span><br><span class="line">    apiGroup: snapshot.storage.k8s.io</span><br><span class="line">  accessModes:</span><br><span class="line">    - ReadWriteOnce</span><br><span class="line">  resources:</span><br><span class="line">    requests:</span><br><span class="line">      storage: 100Gi</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pvc-restore-1.yaml</span></span><br><span class="line">4.3)创建pod引用rbd-pvc-restore-1恢复的pvc</span><br><span class="line"><span class="meta">#</span><span class="bash"> cat pod-restore-1.yaml</span></span><br><span class="line">---</span><br><span class="line">apiVersion: v1</span><br><span class="line">kind: Pod</span><br><span class="line">metadata:</span><br><span class="line">  name: csi-rbd-restore-demo-pod-1</span><br><span class="line">spec:</span><br><span class="line">  containers:</span><br><span class="line">    - name: web-server</span><br><span class="line">      image: nginx</span><br><span class="line">      volumeMounts:</span><br><span class="line">        - name: mypvc</span><br><span class="line">          mountPath: /var/lib/www/html</span><br><span class="line">  volumes:</span><br><span class="line">    - name: mypvc</span><br><span class="line">      persistentVolumeClaim:</span><br><span class="line">        claimName: rbd-pvc-restore-1</span><br><span class="line">        readOnly: false</span><br><span class="line"> </span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl apply -f pod-restore-1.yaml</span></span><br><span class="line">NAME                         READY   STATUS    RESTARTS   AGE</span><br><span class="line">csi-rbd-restore-demo-pod-1   1/1     Running   0          18h</span><br><span class="line"><span class="meta">#</span><span class="bash"> kubectl <span class="built_in">exec</span> -ti csi-rbd-restore-demo-pod-1 -- bash</span></span><br><span class="line">root@csi-rbd-restore-demo-pod-1:/# df -h</span><br><span class="line">Filesystem               Size  Used Avail Use% Mounted on</span><br><span class="line">overlay                  199G  7.4G  192G   4% /</span><br><span class="line">tmpfs                     64M     0   64M   0% /dev</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/fs/cgroup</span><br><span class="line">/dev/mapper/centos-root  199G  7.4G  192G   4% /etc/hosts</span><br><span class="line">shm                       64M     0   64M   0% /dev/shm</span><br><span class="line">/dev/rbd4                 99G  6.8M   99G   1% /var/lib/www/html</span><br><span class="line">tmpfs                    7.8G   12K  7.8G   1% /run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/acpi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/scsi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/firmware</span><br><span class="line">root@csi-rbd-restore-demo-pod-1:/# cd /var/lib/www/html</span><br><span class="line">root@csi-rbd-restore-demo-pod-1:/var/lib/www/html# cat demo/test.txt</span><br><span class="line">snapshot test</span><br><span class="line">abc</span><br><span class="line">root@csi-rbd-restore-demo-pod-1:/var/lib/www/html# cat test/demo.txt</span><br><span class="line">abc</span><br><span class="line"> </span><br><span class="line">// 至此验证扩容后的pvc，二次创建的快照恢复数据功能正常</span><br><span class="line"> </span><br><span class="line">// 查看第一个创建的快照中是否有后续添加的文件数据,如下数据还是第一个快照创建时数据</span><br><span class="line">[root@cka-node-01 rbd]# kubectl exec -ti csi-rbd-restore-demo-pod -- bash</span><br><span class="line">root@csi-rbd-restore-demo-pod:/# df -h</span><br><span class="line">Filesystem               Size  Used Avail Use% Mounted on</span><br><span class="line">overlay                  199G  7.4G  192G   4% /</span><br><span class="line">tmpfs                     64M     0   64M   0% /dev</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/fs/cgroup</span><br><span class="line">/dev/mapper/centos-root  199G  7.4G  192G   4% /etc/hosts</span><br><span class="line">shm                       64M     0   64M   0% /dev/shm</span><br><span class="line">/dev/rbd3                976M  2.6M  958M   1% /var/lib/www/html</span><br><span class="line">tmpfs                    7.8G   12K  7.8G   1% /run/secrets/kubernetes.io/serviceaccount</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/acpi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /proc/scsi</span><br><span class="line">tmpfs                    7.8G     0  7.8G   0% /sys/firmware</span><br><span class="line">root@csi-rbd-restore-demo-pod:/# cd /var/lib/www/html</span><br><span class="line">root@csi-rbd-restore-demo-pod:/var/lib/www/html# cat demo/test.txt</span><br><span class="line">snapshot test</span><br><span class="line">root@csi-rbd-restore-demo-pod:/var/lib/www/html# ls</span><br><span class="line">demo  lost+found</span><br></pre></td></tr></table></figure>

<p><em>完</em></p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
    <article
  id="post-k8s/Kubernetes 新玩法：在 yaml 中编程"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/2020/11/11/k8s/Kubernetes%20%E6%96%B0%E7%8E%A9%E6%B3%95%EF%BC%9A%E5%9C%A8%20yaml%20%E4%B8%AD%E7%BC%96%E7%A8%8B/"
    >Kubernetes 新玩法：在 yaml 中编程.md</a> 
</h2>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/k8s/Kubernetes%20%E6%96%B0%E7%8E%A9%E6%B3%95%EF%BC%9A%E5%9C%A8%20yaml%20%E4%B8%AD%E7%BC%96%E7%A8%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/k8s/">k8s</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="Kubernetes-新玩法：在-yaml-中编程"><a href="#Kubernetes-新玩法：在-yaml-中编程" class="headerlink" title="Kubernetes 新玩法：在 yaml 中编程"></a>Kubernetes 新玩法：在 yaml 中编程</h1><p><strong>引子</strong></p>
<p>性能测试在日常的开发工作中是常规需求，用来摸底服务的性能。</p>
<p>那么如何做性能测试？要么是通过编码的方式完成，写一堆脚本，用完即弃；要么是基于平台，在平台定义的流程中进行。对于后者，通常由于目标场景的复杂性，如部署特定的 workload、观测特定的性能项、网络访问问题等，往往导致性能测试平台要以高成本才能满足不断变化的开发场景的需求。</p>
<p>在云原生的背景下，是否可以更好解决这种问题？</p>
<p>先看两个 yaml 文件：</p>
<ul>
<li>performance-test.yaml 描述了在 K8s 中的操作流程：</li>
</ul>
<ol>
<li><ol>
<li>创建测试用的 Namespace</li>
<li>启动针对 Deployment 创建效率和创建成功率的监控</li>
<li>下述动作重复 N 次：① 使用 workload 模板创建 Deployment；② 等待 Deployment 变为 Ready</li>
<li>删除测试用的 Namespace</li>
</ol>
</li>
<li></li>
</ol>
<ul>
<li>basic-1-pod-deployment.yaml 描述使用的 workload 模板</li>
</ul>
<p>performance-test.yaml ：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">apiVersion: aliyun.com&#x2F;v1alpha1</span><br><span class="line">kind: Beidou</span><br><span class="line">metadata:</span><br><span class="line">  name: performance</span><br><span class="line">  namespace: beidou</span><br><span class="line">spec:</span><br><span class="line">  steps:</span><br><span class="line">  - name: &quot;Create Namespace If Not Exits&quot;</span><br><span class="line">    operations:</span><br><span class="line">    - name: &quot;create namespace&quot;</span><br><span class="line">      type: Task</span><br><span class="line">      op: CreateNamespace</span><br><span class="line">      args:</span><br><span class="line">      - name: NS</span><br><span class="line">        value: beidou</span><br><span class="line">  - name: &quot;Monitor Deployment Creation Efficiency&quot;</span><br><span class="line">    operations:</span><br><span class="line">    - name: &quot;Begin To Monitor Deployment Creation Efficiency&quot;</span><br><span class="line">      type: Task</span><br><span class="line">      op: DeploymentCreationEfficiency</span><br><span class="line">      args:</span><br><span class="line">      - name: NS</span><br><span class="line">        value: beidou</span><br><span class="line">    - name: &quot;Repeat 1 Times&quot;</span><br><span class="line">      type: Task</span><br><span class="line">      op: RepeatNTimes</span><br><span class="line">      args:</span><br><span class="line">      - name: TIMES</span><br><span class="line">        value: &quot;1&quot;</span><br><span class="line">      - name: ACTION</span><br><span class="line">        reference:</span><br><span class="line">          id: deployment-operation</span><br><span class="line">  - name: &quot;Delete namespace&quot;</span><br><span class="line">    operations:</span><br><span class="line">    - name: &quot;delete namespace&quot;</span><br><span class="line">      type: Task</span><br><span class="line">      op: DeleteNamespace</span><br><span class="line">      args:</span><br><span class="line">      - name: NS</span><br><span class="line">        value: beidou</span><br><span class="line">      - name: FORCE</span><br><span class="line">        value: &quot;false&quot;</span><br><span class="line">  references:</span><br><span class="line">  - id: deployment-operation</span><br><span class="line">    steps:</span><br><span class="line">    - name: &quot;Prepare Deployment&quot;</span><br><span class="line">      operations:</span><br><span class="line">      - name: &quot;Prepare Deployment&quot;</span><br><span class="line">        type: Task</span><br><span class="line">        op: PrepareBatchDeployments</span><br><span class="line">        args:</span><br><span class="line">        - name: NS</span><br><span class="line">          value: beidou</span><br><span class="line">        - name: NODE_TYPE</span><br><span class="line">          value: ebm</span><br><span class="line">        - name: BATCH_NUM</span><br><span class="line">          value: &quot;1&quot;</span><br><span class="line">        - name: TEMPLATE</span><br><span class="line">          value: &quot;.&#x2F;templates&#x2F;basic-1-pod-deployment.yaml&quot;</span><br><span class="line">        - name: DEPLOYMENT_REPLICAS</span><br><span class="line">          value: &quot;1&quot;</span><br><span class="line">        - name: DEPLOYMENT_PREFIX</span><br><span class="line">          value: &quot;ebm&quot;</span><br><span class="line">      - name: &quot;Wait For Deployments To Be Ready&quot;</span><br><span class="line">        type: Task</span><br><span class="line">        op: WaitForBatchDeploymentsReady</span><br><span class="line">        args:</span><br><span class="line">        - name: NS</span><br><span class="line">          value: beidou</span><br><span class="line">        - name: TIMEOUT</span><br><span class="line">          value: &quot;3m&quot;</span><br><span class="line">        - name: CHECK_INTERVAL</span><br><span class="line">          value: &quot;2s&quot;</span><br></pre></td></tr></table></figure>

<p>basic-1-pod-deployment.yaml：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">apiVersion: apps&#x2F;v1</span><br><span class="line">kind: Deployment</span><br><span class="line">metadata:</span><br><span class="line">  labels:</span><br><span class="line">    app: basic-1-pod</span><br><span class="line">spec:</span><br><span class="line">  selector:</span><br><span class="line">    matchLabels:</span><br><span class="line">      app: basic-1-pod</span><br><span class="line">  template:</span><br><span class="line">    metadata:</span><br><span class="line">      labels:</span><br><span class="line">        app: basic-1-pod</span><br><span class="line">    spec:</span><br><span class="line">      containers:</span><br><span class="line">      - name: nginx</span><br><span class="line">        image: registry-vpc.cn-hangzhou.aliyuncs.com&#x2F;xxx&#x2F;nginx:1.17.9</span><br><span class="line">        imagePullPolicy: Always</span><br><span class="line">        resources:</span><br><span class="line">          limits:</span><br><span class="line">            cpu: 2</span><br><span class="line">            memory: 4Gi</span><br></pre></td></tr></table></figure>

<p>然后通过一个命令行工具执行 performance-test.yaml：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ beidou server -c ~&#x2F;.kube&#x2F;config services&#x2F;performance-test.yaml</span><br></pre></td></tr></table></figure>

<p>执行效果如下 (每个 Deployment 创建耗时，所有 Deployment 创建耗时的 TP95 值，每个 Deployment 是否创建成功)：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601016578135.webp" alt="img"></p>
<p>这些 metrics 是按照 Prometheus 标准输出，可以被 Prometheus server 收集走，再结合 Grafana 可以可视化展示性能测试数据。</p>
<p>通过在 yaml 中表达想法，编排对 K8s 资源的操作、监控，再也不用为性能测试的实现头疼了 :D</p>
<h2 id=""><a href="#" class="headerlink" title=""></a></h2><p><img src="http://iubest.gitee.io/pic/640-1601016578097.gif" alt="动态黑色音符"></p>
<p><strong>为什么要在 yaml 中编程？</strong></p>
<p>性能测试、回归测试等对于服务质量保障有很大帮助，需要做，但常规的实现方法在初期需要投入较多的时间和精力，新增变更后维护成本比较高。</p>
<p>通常这个过程是以代码的方式实现原子操作，如创建 Deployment、检测 Pod 配置等，然后再组合原子操作来满足需求，如 创建 Deployment -&gt; 等待 Deployment ready -&gt; 检测 Pod 配置等。</p>
<p>有没有办法在实现的过程中既可以尽量低成本实现，又可以复用已有的经验？</p>
<p>可以将原子操作封装为原语，如 CreateDeployment、CheckPod，再通过 yaml 的结构表达流程，那么就可以通过 yaml 而非代码的方式描述想法，又可以复用他人已经写好的 yaml 文件来解决某类场景的需求。</p>
<p>即在 yaml 中编程，减少重复性代码工作，通过 <strong>声明式</strong> 的方式描述逻辑，并以 yaml 文件来满足场景级别的复用。</p>
<p>业界有很多种类型的 <strong>声明式操作</strong> 服务，如运维领域中的<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247494380&idx=1&sn=8cac110d7f50d5a3852a8f69796db963&chksm=fae6e123cd9168359b9bba26f708d2b4013cfed0d592dd1d56daece5040b10367f2f51941a41&mpshare=1&scene=24&srcid=09189RwfTmxbBxMcfb2mLjxz&sharer_sharetime=1600403164443&sharer_shareid=407c90840c4caeeaf9680b1dd38c62ba&key=2ff6efa5b41f0127c9e924be5ce6384145d0c7b612a7633330d08c5fa39fbbfb2ab6c7d4b937423b54f24df15180312494985a7d0d2daa7a62627643cc000fa6dd852a5b9448a9f4044d0c688e5376586d1eef888fecca2dadd2ed1ca94685cce2afb8aa23ee2d4b122ab673e1209256add0569914cbc144befe150d14f9956d&ascene=14&uin=MTIwMjI3NTkwNQ==&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AzxByEV7vLvuybMOAm6iNm4=&pass_ticket=pdKql0fF0rGOXvNr/tToA1+AardNoo77GWcTcNS7PpaVOYI2W/vk8qbSO4P5qmER&wx_header=0&winzoom=1">Ansible</a>、<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247494380&idx=1&sn=8cac110d7f50d5a3852a8f69796db963&chksm=fae6e123cd9168359b9bba26f708d2b4013cfed0d592dd1d56daece5040b10367f2f51941a41&mpshare=1&scene=24&srcid=09189RwfTmxbBxMcfb2mLjxz&sharer_sharetime=1600403164443&sharer_shareid=407c90840c4caeeaf9680b1dd38c62ba&key=2ff6efa5b41f0127c9e924be5ce6384145d0c7b612a7633330d08c5fa39fbbfb2ab6c7d4b937423b54f24df15180312494985a7d0d2daa7a62627643cc000fa6dd852a5b9448a9f4044d0c688e5376586d1eef888fecca2dadd2ed1ca94685cce2afb8aa23ee2d4b122ab673e1209256add0569914cbc144befe150d14f9956d&ascene=14&uin=MTIwMjI3NTkwNQ==&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AzxByEV7vLvuybMOAm6iNm4=&pass_ticket=pdKql0fF0rGOXvNr/tToA1+AardNoo77GWcTcNS7PpaVOYI2W/vk8qbSO4P5qmER&wx_header=0&winzoom=1">SaltStack</a>，Kubernetes 中的<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247494380&idx=1&sn=8cac110d7f50d5a3852a8f69796db963&chksm=fae6e123cd9168359b9bba26f708d2b4013cfed0d592dd1d56daece5040b10367f2f51941a41&mpshare=1&scene=24&srcid=09189RwfTmxbBxMcfb2mLjxz&sharer_sharetime=1600403164443&sharer_shareid=407c90840c4caeeaf9680b1dd38c62ba&key=2ff6efa5b41f0127c9e924be5ce6384145d0c7b612a7633330d08c5fa39fbbfb2ab6c7d4b937423b54f24df15180312494985a7d0d2daa7a62627643cc000fa6dd852a5b9448a9f4044d0c688e5376586d1eef888fecca2dadd2ed1ca94685cce2afb8aa23ee2d4b122ab673e1209256add0569914cbc144befe150d14f9956d&ascene=14&uin=MTIwMjI3NTkwNQ==&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AzxByEV7vLvuybMOAm6iNm4=&pass_ticket=pdKql0fF0rGOXvNr/tToA1+AardNoo77GWcTcNS7PpaVOYI2W/vk8qbSO4P5qmER&wx_header=0&winzoom=1">Argo Workflow</a>、<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247494380&idx=1&sn=8cac110d7f50d5a3852a8f69796db963&chksm=fae6e123cd9168359b9bba26f708d2b4013cfed0d592dd1d56daece5040b10367f2f51941a41&mpshare=1&scene=24&srcid=09189RwfTmxbBxMcfb2mLjxz&sharer_sharetime=1600403164443&sharer_shareid=407c90840c4caeeaf9680b1dd38c62ba&key=2ff6efa5b41f0127c9e924be5ce6384145d0c7b612a7633330d08c5fa39fbbfb2ab6c7d4b937423b54f24df15180312494985a7d0d2daa7a62627643cc000fa6dd852a5b9448a9f4044d0c688e5376586d1eef888fecca2dadd2ed1ca94685cce2afb8aa23ee2d4b122ab673e1209256add0569914cbc144befe150d14f9956d&ascene=14&uin=MTIwMjI3NTkwNQ==&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AzxByEV7vLvuybMOAm6iNm4=&pass_ticket=pdKql0fF0rGOXvNr/tToA1+AardNoo77GWcTcNS7PpaVOYI2W/vk8qbSO4P5qmER&wx_header=0&winzoom=1">clusterloader2</a>。它们的思想整体比较类似，将高频使用的操作封装为原语，使用者通过原语来表述操作逻辑。</p>
<p>通过声明式的方法，将面向 K8s 的操作抽象成 yaml 中的关键词，在 yaml 中提供串行、并行等控制逻辑，那么就可以通过 yaml 文件完整描述想要进行的工作。</p>
<p>这种思想和<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&mid=2247494380&idx=1&sn=8cac110d7f50d5a3852a8f69796db963&chksm=fae6e123cd9168359b9bba26f708d2b4013cfed0d592dd1d56daece5040b10367f2f51941a41&mpshare=1&scene=24&srcid=09189RwfTmxbBxMcfb2mLjxz&sharer_sharetime=1600403164443&sharer_shareid=407c90840c4caeeaf9680b1dd38c62ba&key=2ff6efa5b41f0127c9e924be5ce6384145d0c7b612a7633330d08c5fa39fbbfb2ab6c7d4b937423b54f24df15180312494985a7d0d2daa7a62627643cc000fa6dd852a5b9448a9f4044d0c688e5376586d1eef888fecca2dadd2ed1ca94685cce2afb8aa23ee2d4b122ab673e1209256add0569914cbc144befe150d14f9956d&ascene=14&uin=MTIwMjI3NTkwNQ==&devicetype=Windows+10+x64&version=62090529&lang=zh_CN&exportkey=AzxByEV7vLvuybMOAm6iNm4=&pass_ticket=pdKql0fF0rGOXvNr/tToA1+AardNoo77GWcTcNS7PpaVOYI2W/vk8qbSO4P5qmER&wx_header=0&winzoom=1">Argo Workflow</a> 比较像，但粒度比 Argo 更细，关注在操作函数上：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601016578136.webp" alt="img"></p>
<p>下面简单描述该服务的设计和实现。</p>
<p><strong>设计和实现</strong></p>
<h3 id="1-服务形态"><a href="#1-服务形态" class="headerlink" title="1. 服务形态"></a><strong>1. 服务形态</strong></h3><ul>
<li>使用者在 yaml 中，通过 <strong>声明式</strong> 的方式描述操作逻辑；</li>
<li>以 all-in-one 的二进制工具或 Operator 的方式交付；</li>
<li>服务内置常见原语的实现，以关键字的方式在 yaml 中提供；</li>
<li>支持配置原生 K8s 资源。</li>
</ul>
<h3 id="-1"><a href="#-1" class="headerlink" title=""></a></h3><h3 id="2-设计"><a href="#2-设计" class="headerlink" title="2. 设计"></a><strong>2. 设计</strong></h3><p>该方案的核心在于配置管理的设计，将操作流程配置化，自上而下有如下概念：</p>
<ul>
<li><p><strong>Service</strong>：Modules 或 Tasks 的编排；</p>
</li>
<li><p><strong>Module</strong>：一种任务场景，是操作单元的集合（其中包含 templates/ 目录，表征模板文件的集合，可用来配置 K8s 原生资源）；</p>
</li>
<li><p><strong>Task</strong>：操作单元，使用 plugin 及参数执行操作；</p>
</li>
<li><p><strong>Plugin</strong>：操作指令，类似开发语言中的函数。</p>
</li>
</ul>
<p>抽象目标场景中的通用操作，这些通用操作即为可在 yaml 中使用的原语，对应上述 Plugin：</p>
<ul>
<li><p><strong>K8s 相关</strong></p>
</li>
<li><ul>
<li>CreateNamespace</li>
<li>DeleteNamespace</li>
<li>PrepareSecret</li>
<li>PrepareConfigMap</li>
<li>PrepareBatchDeployments</li>
<li>WaitForBatchDeploymentsReady</li>
<li>etc.</li>
</ul>
</li>
<li><p><strong>观测性相关</strong></p>
</li>
<li><ul>
<li>DeploymentCreationEfficiency</li>
<li>PodCreationEfficiency</li>
<li>etc.</li>
</ul>
</li>
<li><p><strong>检测项相关</strong></p>
</li>
<li><ul>
<li>CheckPodAnnotations</li>
<li>CheckPodObjectInfo</li>
<li>CheckPodInnerStates</li>
<li>etc.</li>
</ul>
</li>
<li><p><strong>控制语句相关</strong></p>
</li>
<li><ul>
<li>RepeatNTimes</li>
<li>etc.</li>
</ul>
</li>
</ul>
<p>上述 4 个概念的关系如下：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601016578094.webp" alt="img"></p>
<p>示例可参见文章开头的 yaml 文件，对应形式二。</p>
<h3 id="-2"><a href="#-2" class="headerlink" title=""></a></h3><h3 id="3-核心实现"><a href="#3-核心实现" class="headerlink" title="3. 核心实现"></a><strong>3. 核心实现</strong></h3><p>CRD 设计：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">package v1alpha1</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">  corev1 &quot;k8s.io&#x2F;api&#x2F;core&#x2F;v1&quot;</span><br><span class="line">  metav1 &quot;k8s.io&#x2F;apimachinery&#x2F;pkg&#x2F;apis&#x2F;meta&#x2F;v1&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouType is the type related to Beidou execution.</span><br><span class="line">type BeidouType string</span><br><span class="line"></span><br><span class="line">const (</span><br><span class="line">  &#x2F;&#x2F; BeidouTask represents the Task execution type.</span><br><span class="line">  BeidouTask BeidouType &#x3D; &quot;Task&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; +genclient</span><br><span class="line">&#x2F;&#x2F; +k8s:deepcopy-gen:interfaces&#x3D;k8s.io&#x2F;apimachinery&#x2F;pkg&#x2F;runtime.Object</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; Beidou represents a crd used to describe serices.</span><br><span class="line">type Beidou struct &#123;</span><br><span class="line">  metav1.TypeMeta   &#96;json:&quot;,inline&quot;&#96;</span><br><span class="line">  metav1.ObjectMeta &#96;json:&quot;metadata,omitempty&quot; protobuf:&quot;bytes,1,opt,name&#x3D;metadata&quot;&#96;</span><br><span class="line"></span><br><span class="line">  Spec   BeidouSpec   &#96;json:&quot;spec,omitempty&quot; protobuf:&quot;bytes,2,opt,name&#x3D;spec&quot;&#96;</span><br><span class="line">  Status BeidouStatus &#96;json:&quot;status,omitempty&quot; protobuf:&quot;bytes,3,opt,name&#x3D;status&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouSpec is the spec of a Beidou.</span><br><span class="line">type BeidouSpec struct &#123;</span><br><span class="line">  Steps      []BeidouStep      &#96;json:&quot;steps&quot; protobuf:&quot;bytes,1,opt,name&#x3D;steps&quot;&#96;</span><br><span class="line">  References []BeidouReference &#96;json:&quot;references&quot; protobuf:&quot;bytes,2,opt,name&#x3D;references&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouStep is the spec of step.</span><br><span class="line">type BeidouStep struct &#123;</span><br><span class="line">  Name       string            &#96;json:&quot;name&quot; protobuf:&quot;bytes,1,opt,name&#x3D;name&quot;&#96;</span><br><span class="line">  Operations []BeidouOperation &#96;json:&quot;operations&quot; protobuf:&quot;bytes,2,opt,name&#x3D;operations&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouOperation is the spec of operation.</span><br><span class="line">type BeidouOperation struct &#123;</span><br><span class="line">  Name string      &#96;json:&quot;name&quot; protobuf:&quot;bytes,1,opt,name&#x3D;name&quot;&#96;</span><br><span class="line">  Type BeidouType  &#96;json:&quot;type&quot; protobuf:&quot;bytes,2,opt,name&#x3D;type&quot;&#96;</span><br><span class="line">  Op   string      &#96;json:&quot;op&quot; protobuf:&quot;bytes,3,opt,name&#x3D;op&quot;&#96;</span><br><span class="line">  Args []BeidouArg &#96;json:&quot;args&quot; protobuf:&quot;bytes,4,opt,name&#x3D;args&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouArg is the spec of arg.</span><br><span class="line">type BeidouArg struct &#123;</span><br><span class="line">  Name        string                   &#96;json:&quot;name&quot; protobuf:&quot;bytes,1,opt,name&#x3D;name&quot;&#96;</span><br><span class="line">  Value       string                   &#96;json:&quot;value,omitempty&quot; protobuf:&quot;bytes,2,opt,name&#x3D;value&quot;&#96;</span><br><span class="line">  Reference   BeidouOperationReference &#96;json:&quot;reference,omitempty&quot; protobuf:&quot;bytes,3,opt,name&#x3D;reference&quot;&#96;</span><br><span class="line">  Tolerations []corev1.Toleration      &#96;json:&quot;tolerations,omitempty&quot; protobuf:&quot;bytes,4,opt,name&#x3D;tolerations&quot;&#96;</span><br><span class="line">  Checking    []string                 &#96;json:&quot;checking,omitempty&quot; protobuf:&quot;bytes,5,opt,name&#x3D;checking&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouOperationReference is the spec of operation reference.</span><br><span class="line">type BeidouOperationReference struct &#123;</span><br><span class="line">  ID string &#96;json:&quot;id&quot; protobuf:&quot;bytes,1,opt,name&#x3D;id&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouReference is the spec of reference.</span><br><span class="line">type BeidouReference struct &#123;</span><br><span class="line">  ID    string       &#96;json:&quot;id&quot; protobuf:&quot;bytes,1,opt,name&#x3D;id&quot;&#96;</span><br><span class="line">  Steps []BeidouStep &#96;json:&quot;steps&quot; protobuf:&quot;bytes,2,opt,name&#x3D;steps&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouStatus represents the current state of a Beidou.</span><br><span class="line">type BeidouStatus struct &#123;</span><br><span class="line">  Message string &#96;json:&quot;message&quot; protobuf:&quot;bytes,1,opt,name&#x3D;message&quot;&#96;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; +k8s:deepcopy-gen:interfaces&#x3D;k8s.io&#x2F;apimachinery&#x2F;pkg&#x2F;runtime.Object</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; BeidouList is a collection of Beidou.</span><br><span class="line">type BeidouList struct &#123;</span><br><span class="line">  metav1.TypeMeta &#96;json:&quot;,inline&quot;&#96;</span><br><span class="line">  metav1.ListMeta &#96;json:&quot;metadata&quot; protobuf:&quot;bytes,1,opt,name&#x3D;metadata&quot;&#96;</span><br><span class="line"></span><br><span class="line">  Items []Beidou &#96;json:&quot;items&quot; protobuf:&quot;bytes,2,opt,name&#x3D;items&quot;&#96;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>核心流程：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; ExecSteps executes steps.</span><br><span class="line">func ExecSteps(ctx context.Context, steps []v1alpha1.BeidouStep, references []v1alpha1.BeidouReference) error &#123;</span><br><span class="line">    logger, _ :&#x3D; ctx.Value(CtxLogger).(*log.Entry)</span><br><span class="line"></span><br><span class="line">  var hasMonitored bool</span><br><span class="line">  for i, step :&#x3D; range steps &#123;</span><br><span class="line">    for j, op :&#x3D; range step.Operations &#123;</span><br><span class="line">      switch op.Op &#123;</span><br><span class="line">      case &quot;DeploymentCreationEfficiency&quot;:</span><br><span class="line">        if !hasMonitored &#123;</span><br><span class="line">          defer func() &#123;</span><br><span class="line">            err :&#x3D; monitor.Output()</span><br><span class="line">            if err !&#x3D; nil &#123;</span><br><span class="line">              logger.Errorf(&quot;Failed to output: %s&quot;, err)</span><br><span class="line">            &#125;</span><br><span class="line">          &#125;()</span><br><span class="line">        &#125;</span><br><span class="line">        hasMonitored &#x3D; true</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      err :&#x3D; ExecOperation(ctx, op, references)</span><br><span class="line">      if err !&#x3D; nil &#123;</span><br><span class="line">        return fmt.Errorf(&quot;failed to run operation %s: %s&quot;, op.Name, err)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return nil</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; ExecOperation executes operation.</span><br><span class="line">func ExecOperation(ctx context.Context, op v1alpha1.BeidouOperation, references []v1alpha1.BeidouReference) error &#123;</span><br><span class="line">  switch op.Type &#123;</span><br><span class="line">  case v1alpha1.BeidouTask:</span><br><span class="line">    if !tasks.IsRegistered(op.Op) &#123;</span><br><span class="line">      return ErrNotRegistered</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if !tasks.DoesSupportReference(op.Op) &#123;</span><br><span class="line">      return ExecTask(ctx, op.Op, op.Args)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return ExecTaskWithRefer(ctx, op.Op, op.Args, references)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return nil</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; ExecTask executes a task.</span><br><span class="line">func ExecTask(ctx context.Context, opname string, args []v1alpha1.BeidouArg) error &#123;</span><br><span class="line">  switch opname &#123;</span><br><span class="line">  case tasks.CreateNamespace:</span><br><span class="line">    var ns string</span><br><span class="line">    for _, arg :&#x3D; range args &#123;</span><br><span class="line">      switch arg.Name &#123;</span><br><span class="line">      case &quot;NS&quot;:</span><br><span class="line">        ns &#x3D; arg.Value</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return op.CreateNamespace(ctx, ns)</span><br><span class="line">    &#x2F;&#x2F; ...</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;&#x2F; ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; ExecTaskWithRefer executes a task with reference.</span><br><span class="line">func ExecTaskWithRefer(ctx context.Context, opname string, args []v1alpha1.BeidouArg, references []v1alpha1.BeidouReference) error &#123;</span><br><span class="line">  switch opname &#123;</span><br><span class="line">  case tasks.RepeatNTimes:</span><br><span class="line">    var times int</span><br><span class="line">    var steps []v1alpha1.BeidouStep</span><br><span class="line">    var err error</span><br><span class="line">    for _, arg :&#x3D; range args &#123;</span><br><span class="line">      switch arg.Name &#123;</span><br><span class="line">      case &quot;TIMES&quot;:</span><br><span class="line">        times, err &#x3D; strconv.Atoi(arg.Value)</span><br><span class="line">        if err !&#x3D; nil &#123;</span><br><span class="line">          return ErrParseArgs</span><br><span class="line">        &#125;</span><br><span class="line">      case &quot;ACTION&quot;:</span><br><span class="line">        for _, refer :&#x3D; range references &#123;</span><br><span class="line">          if refer.ID &#x3D;&#x3D; arg.Reference.ID &#123;</span><br><span class="line">            steps &#x3D; refer.Steps</span><br><span class="line">            break</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return RepeatNTimes(ctx, times, steps)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return ErrNotImplemented</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>操作原语的实现示例：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; PodAnnotations is an operation used to check whether annotations of Pod are expected.</span><br><span class="line">func PodAnnotations(ctx context.Context, data PodAnnotationsData) error &#123;</span><br><span class="line">  kclient, ok :&#x3D; ctx.Value(tasks.KubernetesClient).(kubernetes.Interface)</span><br><span class="line">  if !ok &#123;</span><br><span class="line">    return tasks.ErrNoKubernetesClient</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  pods, err :&#x3D; kclient.CoreV1().Pods(data.Namespace).List(metav1.ListOptions&#123;&#125;)</span><br><span class="line">  if err !&#x3D; nil &#123;</span><br><span class="line">    return fmt.Errorf(&quot;failed to list pods in ns %s: %s&quot;, data.Namespace, err)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  for _, pod :&#x3D; range pods.Items &#123;</span><br><span class="line">    if pod.Annotations &#x3D;&#x3D; nil &#123;</span><br><span class="line">      return fmt.Errorf(&quot;pod %s in ns %s has no annotations&quot;, pod.Name, data.Namespace)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    for _, annotation :&#x3D; range data.Exists &#123;</span><br><span class="line">      if _, exists :&#x3D; pod.Annotations[annotation]; !exists &#123;</span><br><span class="line">        return fmt.Errorf(&quot;annotation %s does not exist in pod %s in ns %s&quot;, annotation, pod.Name, data.Namespace)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    for k, v :&#x3D; range data.Equal &#123;</span><br><span class="line">      if pod.Annotations[k] !&#x3D; v &#123;</span><br><span class="line">        return fmt.Errorf(&quot;value of annotation %s is not %s in pod %s in ns %s&quot;, k, v, pod.Name, data.Namespace)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  return nil</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>后续</strong></p>
<p>目前阿里云容器服务团队内部已经实现了初版，已用于部分云产品的内部性能测试以及常规的回归测试，很大程度上提升了我们的工作效率。</p>
<p>在 yaml 中编程，是对云原生场景下声明式操作的体现，也是对声明式服务的一种实践。对于常规工作场景中重复编码或重复操作，可考虑类似的方式进行满足。</p>
<p>欢迎大家对这样的服务形态和项目进行讨论，探索这种模式的价值。</p>
<p><strong>阿里云容器服务持续招聘，欢迎加入我们，一起在 K8s、边缘计算、Serverless 等领域开拓，让当前变得更美好，也为未来带来可能性！联系邮箱：<a href="mailto:&#x66;&#108;&#x79;&#x65;&#x72;&#x2e;&#x7a;&#x79;&#102;&#64;&#x61;&#108;&#x69;&#x62;&#x61;&#x62;&#x61;&#x2d;&#x69;&#110;&#x63;&#46;&#99;&#111;&#x6d;">&#x66;&#108;&#x79;&#x65;&#x72;&#x2e;&#x7a;&#x79;&#102;&#64;&#x61;&#108;&#x69;&#x62;&#x61;&#x62;&#x61;&#x2d;&#x69;&#110;&#x63;&#46;&#99;&#111;&#x6d;</a></strong></p>
<h3 id="References"><a href="#References" class="headerlink" title="References"></a><strong>References</strong></h3><ul>
<li><strong>Ansible</strong>：<a target="_blank" rel="noopener" href="https://docs.ansible.com/ansible/latest/index.html">https://docs.ansible.com/ansible/latest/index.html</a></li>
<li><strong>SaltStack</strong>：<a target="_blank" rel="noopener" href="https://docs.saltstack.com/en/latest/">https://docs.saltstack.com/en/latest/</a></li>
<li><strong>Argo Workflow</strong>：<a target="_blank" rel="noopener" href="https://github.com/argoproj/argo">https://github.com/argoproj/argo</a></li>
<li><strong>clusterloader2</strong>：<a target="_blank" rel="noopener" href="https://github.com/kubernetes/perf-tests/tree/master/clusterloader2">https://github.com/kubernetes/perf-tests/tree/master/clusterloader2</a></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/k8s/" rel="tag">k8s</a></li></ul>

    </footer>
  </div>

    
 
   
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <a class="extend prev" rel="prev" href="/page/9/">上一页</a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/8/">8</a><a class="page-number" href="/page/9/">9</a><span class="page-number current">10</span><a class="page-number" href="/page/11/">11</a><a class="page-number" href="/page/12/">12</a><span class="space">&hellip;</span><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/11/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>